1 /*
2 * Copyright (C) 2008 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 package com.android.launcher3;
18
19 import android.animation.Animator;
20 import android.animation.AnimatorListenerAdapter;
21 import android.animation.AnimatorSet;
22 import android.animation.ObjectAnimator;
23 import android.animation.PropertyValuesHolder;
24 import android.animation.ValueAnimator;
25 import android.annotation.SuppressLint;
26 import android.annotation.TargetApi;
27 import android.app.Activity;
28 import android.app.ActivityManager;
29 import android.app.ActivityOptions;
30 import android.app.AlertDialog;
31 import android.app.SearchManager;
32 import android.appwidget.AppWidgetHostView;
33 import android.appwidget.AppWidgetManager;
34 import android.appwidget.AppWidgetProviderInfo;
35 import android.content.ActivityNotFoundException;
36 import android.content.BroadcastReceiver;
37 import android.content.ComponentCallbacks2;
38 import android.content.ComponentName;
39 import android.content.ContentResolver;
40 import android.content.Context;
41 import android.content.DialogInterface;
42 import android.content.Intent;
43 import android.content.IntentFilter;
44 import android.content.IntentSender;
45 import android.content.SharedPreferences;
46 import android.content.pm.ActivityInfo;
47 import android.content.pm.ApplicationInfo;
48 import android.content.pm.PackageManager;
49 import android.content.pm.PackageManager.NameNotFoundException;
50 import android.content.res.Configuration;
51 import android.database.ContentObserver;
52 import android.database.sqlite.SQLiteDatabase;
53 import android.graphics.Bitmap;
54 import android.graphics.Canvas;
55 import android.graphics.Color;
56 import android.graphics.PorterDuff;
57 import android.graphics.Rect;
58 import android.graphics.drawable.ColorDrawable;
59 import android.graphics.drawable.Drawable;
60 import android.net.Uri;
61 import android.os.AsyncTask;
62 import android.os.Build;
63 import android.os.Bundle;
64 import android.os.Environment;
65 import android.os.Handler;
66 import android.os.Message;
67 import android.os.StrictMode;
68 import android.os.SystemClock;
69 import android.text.Selection;
70 import android.text.SpannableStringBuilder;
71 import android.text.TextUtils;
72 import android.text.method.TextKeyListener;
73 import android.util.Log;
74 import android.view.Display;
75 import android.view.Gravity;
76 import android.view.HapticFeedbackConstants;
77 import android.view.KeyEvent;
78 import android.view.LayoutInflater;
79 import android.view.Menu;
80 import android.view.MotionEvent;
81 import android.view.Surface;
82 import android.view.View;
83 import android.view.View.OnClickListener;
84 import android.view.View.OnLongClickListener;
85 import android.view.ViewGroup;
86 import android.view.ViewStub;
87 import android.view.ViewTreeObserver;
88 import android.view.Window;
89 import android.view.WindowManager;
90 import android.view.accessibility.AccessibilityEvent;
91 import android.view.inputmethod.InputMethodManager;
92 import android.widget.Advanceable;
93 import android.widget.FrameLayout;
94 import android.widget.ImageView;
95 import android.widget.TextView;
96 import android.widget.Toast;
97
98 import com.android.launcher3.DropTarget.DragObject;
99 import com.android.launcher3.PagedView.PageSwitchListener;
100 import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
101 import com.android.launcher3.allapps.AllAppsContainerView;
102 import com.android.launcher3.compat.AppWidgetManagerCompat;
103 import com.android.launcher3.compat.LauncherActivityInfoCompat;
104 import com.android.launcher3.compat.LauncherAppsCompat;
105 import com.android.launcher3.compat.UserHandleCompat;
106 import com.android.launcher3.compat.UserManagerCompat;
107 import com.android.launcher3.model.WidgetsModel;
108 import com.android.launcher3.util.LongArrayMap;
109 import com.android.launcher3.util.Thunk;
110 import com.android.launcher3.widget.PendingAddWidgetInfo;
111 import com.android.launcher3.widget.WidgetHostViewLoader;
112 import com.android.launcher3.widget.WidgetsContainerView;
113
114 import java.io.DataInputStream;
115 import java.io.DataOutputStream;
116 import java.io.File;
117 import java.io.FileDescriptor;
118 import java.io.FileNotFoundException;
119 import java.io.FileOutputStream;
120 import java.io.IOException;
121 import java.io.PrintWriter;
122 import java.lang.reflect.InvocationTargetException;
123 import java.lang.reflect.Method;
124 import java.text.DateFormat;
125 import java.util.ArrayList;
126 import java.util.Collection;
127 import java.util.Date;
128 import java.util.HashMap;
129 import java.util.HashSet;
130 import java.util.List;
131 import java.util.concurrent.atomic.AtomicInteger;
132
133 /**
134 * Default launcher application.
135 */
136 public class Launcher extends Activity
137 implements View.OnClickListener, OnLongClickListener, LauncherModel.Callbacks,
138 View.OnTouchListener, PageSwitchListener, LauncherProviderChangeListener,
139 LauncherStateTransitionAnimation.Callbacks {
140 static final String TAG = "Launcher";
141 static final boolean LOGD = false;
142
143 // Temporary flag
144 static final boolean DISABLE_ALL_APPS_SEARCH_INTEGRATION = true;
145
146 static final boolean PROFILE_STARTUP = false;
147 static final boolean DEBUG_WIDGETS = true;
148 static final boolean DEBUG_STRICT_MODE = false;
149 static final boolean DEBUG_RESUME_TIME = false;
150 static final boolean DEBUG_DUMP_LOG = false;
151
152 static final boolean ENABLE_DEBUG_INTENTS = false; // allow DebugIntents to run
153
154 private static final int REQUEST_CREATE_SHORTCUT = 1;
155 private static final int REQUEST_CREATE_APPWIDGET = 5;
156 private static final int REQUEST_PICK_APPWIDGET = 9;
157 private static final int REQUEST_PICK_WALLPAPER = 10;
158
159 private static final int REQUEST_BIND_APPWIDGET = 11;
160 private static final int REQUEST_RECONFIGURE_APPWIDGET = 12;
161
162 private static final int WORKSPACE_BACKGROUND_GRADIENT = 0;
163 private static final int WORKSPACE_BACKGROUND_TRANSPARENT = 1;
164 private static final int WORKSPACE_BACKGROUND_BLACK = 2;
165
166 /**
167 * IntentStarter uses request codes starting with this. This must be greater than all activity
168 * request codes used internally.
169 */
170 protected static final int REQUEST_LAST = 100;
171
172 static final String EXTRA_SHORTCUT_DUPLICATE = "duplicate";
173
174 static final int SCREEN_COUNT = 5;
175
176 // To turn on these properties, type
177 // adb shell setprop log.tag.PROPERTY_NAME [VERBOSE | SUPPRESS]
178 static final String DUMP_STATE_PROPERTY = "launcher_dump_state";
179
180 // The Intent extra that defines whether to ignore the launch animation
181 static final String INTENT_EXTRA_IGNORE_LAUNCH_ANIMATION =
182 "com.android.launcher3.intent.extra.shortcut.INGORE_LAUNCH_ANIMATION";
183
184 // Type: int
185 private static final String RUNTIME_STATE_CURRENT_SCREEN = "launcher.current_screen";
186 // Type: int
187 private static final String RUNTIME_STATE = "launcher.state";
188 // Type: int
189 private static final String RUNTIME_STATE_PENDING_ADD_CONTAINER = "launcher.add_container";
190 // Type: int
191 private static final String RUNTIME_STATE_PENDING_ADD_SCREEN = "launcher.add_screen";
192 // Type: int
193 private static final String RUNTIME_STATE_PENDING_ADD_CELL_X = "launcher.add_cell_x";
194 // Type: int
195 private static final String RUNTIME_STATE_PENDING_ADD_CELL_Y = "launcher.add_cell_y";
196 // Type: int
197 private static final String RUNTIME_STATE_PENDING_ADD_SPAN_X = "launcher.add_span_x";
198 // Type: int
199 private static final String RUNTIME_STATE_PENDING_ADD_SPAN_Y = "launcher.add_span_y";
200 // Type: parcelable
201 private static final String RUNTIME_STATE_PENDING_ADD_WIDGET_INFO = "launcher.add_widget_info";
202 // Type: parcelable
203 private static final String RUNTIME_STATE_PENDING_ADD_WIDGET_ID = "launcher.add_widget_id";
204 // Type: int[]
205 private static final String RUNTIME_STATE_VIEW_IDS = "launcher.view_ids";
206
207 static final String INTRO_SCREEN_DISMISSED = "launcher.intro_screen_dismissed";
208 static final String FIRST_RUN_ACTIVITY_DISPLAYED = "launcher.first_run_activity_displayed";
209
210 static final String FIRST_LOAD_COMPLETE = "launcher.first_load_complete";
211 static final String ACTION_FIRST_LOAD_COMPLETE =
212 "com.android.launcher3.action.FIRST_LOAD_COMPLETE";
213
214 public static final String SHOW_WEIGHT_WATCHER = "debug.show_mem";
215 public static final boolean SHOW_WEIGHT_WATCHER_DEFAULT = false;
216
217 private static final String QSB_WIDGET_ID = "qsb_widget_id";
218 private static final String QSB_WIDGET_PROVIDER = "qsb_widget_provider";
219
220 public static final String USER_HAS_MIGRATED = "launcher.user_migrated_from_old_data";
221
222 /** The different states that Launcher can be in. */
223 enum State { NONE, WORKSPACE, APPS, APPS_SPRING_LOADED, WIDGETS, WIDGETS_SPRING_LOADED };
224 @Thunk State mState = State.WORKSPACE;
225 @Thunk LauncherStateTransitionAnimation mStateTransitionAnimation;
226
227 private boolean mIsSafeModeEnabled;
228
229 LauncherOverlayCallbacks mLauncherOverlayCallbacks = new LauncherOverlayCallbacksImpl();
230 LauncherOverlay mLauncherOverlay;
231 InsettableFrameLayout mLauncherOverlayContainer;
232
233 static final int APPWIDGET_HOST_ID = 1024;
234 public static final int EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT = 300;
235 private static final int ON_ACTIVITY_RESULT_ANIMATION_DELAY = 500;
236 private static final int ACTIVITY_START_DELAY = 1000;
237
238 private HashMap<Integer, Integer> mItemIdToViewId = new HashMap<Integer, Integer>();
239 private static final AtomicInteger sNextGeneratedId = new AtomicInteger(1);
240
241 // How long to wait before the new-shortcut animation automatically pans the workspace
242 private static int NEW_APPS_PAGE_MOVE_DELAY = 500;
243 private static int NEW_APPS_ANIMATION_INACTIVE_TIMEOUT_SECONDS = 5;
244 @Thunk static int NEW_APPS_ANIMATION_DELAY = 500;
245
246 private final BroadcastReceiver mCloseSystemDialogsReceiver
247 = new CloseSystemDialogsIntentReceiver();
248 private final ContentObserver mWidgetObserver = new AppWidgetResetObserver();
249
250 private LayoutInflater mInflater;
251
252 @Thunk Workspace mWorkspace;
253 private View mLauncherView;
254 private View mPageIndicators;
255 @Thunk DragLayer mDragLayer;
256 private DragController mDragController;
257 private View mWeightWatcher;
258
259 private AppWidgetManagerCompat mAppWidgetManager;
260 private LauncherAppWidgetHost mAppWidgetHost;
261
262 @Thunk ItemInfo mPendingAddInfo = new ItemInfo();
263 private LauncherAppWidgetProviderInfo mPendingAddWidgetInfo;
264 private int mPendingAddWidgetId = -1;
265
266 private int[] mTmpAddItemCellCoordinates = new int[2];
267
268 private Hotseat mHotseat;
269 private ViewGroup mOverviewPanel;
270
271 private View mAllAppsButton;
272
273 private SearchDropTargetBar mSearchDropTargetBar;
274
275 // Main container view for the all apps screen.
276 @Thunk AllAppsContainerView mAppsView;
277
278 // Main container view and the model for the widget tray screen.
279 @Thunk WidgetsContainerView mWidgetsView;
280 @Thunk WidgetsModel mWidgetsModel;
281
282 private boolean mAutoAdvanceRunning = false;
283 private AppWidgetHostView mQsb;
284
285 private Bundle mSavedState;
286 // We set the state in both onCreate and then onNewIntent in some cases, which causes both
287 // scroll issues (because the workspace may not have been measured yet) and extra work.
288 // Instead, just save the state that we need to restore Launcher to, and commit it in onResume.
289 private State mOnResumeState = State.NONE;
290
291 private SpannableStringBuilder mDefaultKeySsb = null;
292
293 @Thunk boolean mWorkspaceLoading = true;
294
295 private boolean mPaused = true;
296 private boolean mRestoring;
297 private boolean mWaitingForResult;
298 private boolean mOnResumeNeedsLoad;
299
300 private ArrayList<Runnable> mBindOnResumeCallbacks = new ArrayList<Runnable>();
301 private ArrayList<Runnable> mOnResumeCallbacks = new ArrayList<Runnable>();
302
303 private Bundle mSavedInstanceState;
304
305 private LauncherModel mModel;
306 private IconCache mIconCache;
307 @Thunk boolean mUserPresent = true;
308 private boolean mVisible = false;
309 private boolean mHasFocus = false;
310 private boolean mAttached = false;
311
312 @Thunk static LocaleConfiguration sLocaleConfiguration = null;
313
314 private static LongArrayMap<FolderInfo> sFolders = new LongArrayMap<>();
315
316 private View.OnTouchListener mHapticFeedbackTouchListener;
317
318 // Related to the auto-advancing of widgets
319 private final int ADVANCE_MSG = 1;
320 private final int mAdvanceInterval = 20000;
321 private final int mAdvanceStagger = 250;
322 private long mAutoAdvanceSentTime;
323 private long mAutoAdvanceTimeLeft = -1;
324 @Thunk HashMap<View, AppWidgetProviderInfo> mWidgetsToAdvance =
325 new HashMap<View, AppWidgetProviderInfo>();
326
327 // Determines how long to wait after a rotation before restoring the screen orientation to
328 // match the sensor state.
329 private final int mRestoreScreenOrientationDelay = 500;
330
331 @Thunk Drawable mWorkspaceBackgroundDrawable;
332
333 private final ArrayList<Integer> mSynchronouslyBoundPages = new ArrayList<Integer>();
334 private static final boolean DISABLE_SYNCHRONOUS_BINDING_CURRENT_PAGE = false;
335
336 static final ArrayList<String> sDumpLogs = new ArrayList<String>();
337 static Date sDateStamp = new Date();
338 static DateFormat sDateFormat =
339 DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT);
340 static long sRunStart = System.currentTimeMillis();
341 static final String CORRUPTION_EMAIL_SENT_KEY = "corruptionEmailSent";
342
343 // We only want to get the SharedPreferences once since it does an FS stat each time we get
344 // it from the context.
345 private SharedPreferences mSharedPrefs;
346
347 // Holds the page that we need to animate to, and the icon views that we need to animate up
348 // when we scroll to that page on resume.
349 @Thunk ImageView mFolderIconImageView;
350 private Bitmap mFolderIconBitmap;
351 private Canvas mFolderIconCanvas;
352 private Rect mRectForFolderAnimation = new Rect();
353
354 private DeviceProfile mDeviceProfile;
355
356 // This is set to the view that launched the activity that navigated the user away from
357 // launcher. Since there is no callback for when the activity has finished launching, enable
358 // the press state and keep this reference to reset the press state when we return to launcher.
359 private BubbleTextView mWaitingForResume;
360
361 protected static HashMap<String, CustomAppWidget> sCustomAppWidgets =
362 new HashMap<String, CustomAppWidget>();
363
364 private static final boolean ENABLE_CUSTOM_WIDGET_TEST = false;
365 static {
366 if (ENABLE_CUSTOM_WIDGET_TEST) {
367 sCustomAppWidgets.put(DummyWidget.class.getName(), new DummyWidget());
368 }
369 }
370
371 // TODO: remove this field and call method directly when Launcher3 can depend on M APIs
372 private static Method sClipRevealMethod = null;
373 static {
374 Class<?> activityOptionsClass = ActivityOptions.class;
375 try {
376 sClipRevealMethod = activityOptionsClass.getDeclaredMethod("makeClipRevealAnimation",
377 View.class, int.class, int.class, int.class, int.class);
378 } catch (Exception e) {
379 // Earlier version
380 }
381 }
382
383 @Thunk Runnable mBuildLayersRunnable = new Runnable() {
384 public void run() {
385 if (mWorkspace != null) {
386 mWorkspace.buildPageHardwareLayers();
387 }
388 }
389 };
390
391 private static PendingAddArguments sPendingAddItem;
392
393 @Thunk static class PendingAddArguments {
394 int requestCode;
395 Intent intent;
396 long container;
397 long screenId;
398 int cellX;
399 int cellY;
400 int appWidgetId;
401 }
402
403 private Stats mStats;
404
405 FocusIndicatorView mFocusHandler;
406
407 @Override
408 protected void onCreate(Bundle savedInstanceState) {
409 if (DEBUG_STRICT_MODE) {
410 StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
411 .detectDiskReads()
412 .detectDiskWrites()
413 .detectNetwork() // or .detectAll() for all detectable problems
414 .penaltyLog()
415 .build());
416 StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
417 .detectLeakedSqlLiteObjects()
418 .detectLeakedClosableObjects()
419 .penaltyLog()
420 .penaltyDeath()
421 .build());
422 }
423
424 if (mLauncherCallbacks != null) {
425 mLauncherCallbacks.preOnCreate();
426 }
427
428 super.onCreate(savedInstanceState);
429
430 LauncherAppState.setApplicationContext(getApplicationContext());
431 LauncherAppState app = LauncherAppState.getInstance();
432 LauncherAppState.getLauncherProvider().setLauncherProviderChangeListener(this);
433
434 // Load configuration-specific DeviceProfile
435 mDeviceProfile = getResources().getConfiguration().orientation
436 == Configuration.ORIENTATION_LANDSCAPE ?
437 app.getInvariantDeviceProfile().landscapeProfile
438 : app.getInvariantDeviceProfile().portraitProfile;
439
440 // TODO: Move this to icon cache.
441 Utilities.setIconSize(mDeviceProfile.iconSizePx);
442
443 // the LauncherApplication should call this, but in case of Instrumentation it might not be prese🔵
444 mSharedPrefs = getSharedPreferences(LauncherAppState.getSharedPreferencesKey(),
445 Context.MODE_PRIVATE);
446 mIsSafeModeEnabled = getPackageManager().isSafeMode();
447 mModel = app.setLauncher(this);
448 mIconCache = app.getIconCache();
449
450 mDragController = new DragController(this);
451 mInflater = getLayoutInflater();
452 mStateTransitionAnimation = new LauncherStateTransitionAnimation(this, this);
453
454 mStats = new Stats(this);
455
456 mAppWidgetManager = AppWidgetManagerCompat.getInstance(this);
457
458 mAppWidgetHost = new LauncherAppWidgetHost(this, APPWIDGET_HOST_ID);
459 mAppWidgetHost.startListening();
460
461 // If we are getting an onCreate, we can actually preempt onResume and unset mPaused here,
462 // this also ensures that any synchronous binding below doesn't re-trigger another
463 // LauncherModel load.
464 mPaused = false;
465
466 if (PROFILE_STARTUP) {
467 android.os.Debug.startMethodTracing(
468 Environment.getExternalStorageDirectory() + "/launcher");
469 }
470
471 checkForLocaleChange();
472 setContentView(R.layout.launcher);
473
474 setupViews();
475 mDeviceProfile.layout(this);
476
477 registerContentObservers();
478
479 lockAllApps();
480
481 mSavedState = savedInstanceState;
482 restoreState(mSavedState);
483
484 if (PROFILE_STARTUP) {
485 android.os.Debug.stopMethodTracing();
486 }
487
488 if (!mRestoring) {
489 if (DISABLE_SYNCHRONOUS_BINDING_CURRENT_PAGE) {
490 // If the user leaves launcher, then we should just load items asynchronously when
491 // they return.
492 mModel.startLoader(PagedView.INVALID_RESTORE_PAGE);
493 } else {
494 // We only load the page synchronously if the user rotates (or triggers a
495 // configuration change) while launcher is in the foreground
496 mModel.startLoader(mWorkspace.getRestorePage());
497 }
498 }
499
500 // For handling default keys
501 mDefaultKeySsb = new SpannableStringBuilder();
502 Selection.setSelection(mDefaultKeySsb, 0);
503
504 IntentFilter filter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
505 registerReceiver(mCloseSystemDialogsReceiver, filter);
506
507 // On large interfaces, we want the screen to auto-rotate based on the current orientation
508 unlockScreenOrientation(true);
509
510 if (mLauncherCallbacks != null) {
511 mLauncherCallbacks.onCreate(savedInstanceState);
512 if (mLauncherCallbacks.hasLauncherOverlay()) {
513 ViewStub stub = (ViewStub) findViewById(R.id.launcher_overlay_stub);
514 mLauncherOverlayContainer = (InsettableFrameLayout) stub.inflate();
515 mLauncherOverlay = mLauncherCallbacks.setLauncherOverlayView(
516 mLauncherOverlayContainer, mLauncherOverlayCallbacks);
517 mWorkspace.setLauncherOverlay(mLauncherOverlay);
518 }
519 }
520
521 if (shouldShowIntroScreen()) {
522 showIntroScreen();
523 } else {
524 showFirstRunActivity();
525 showFirstRunClings();
526 }
527 }
528
529 private LauncherCallbacks mLauncherCallbacks;
530
531 public void onPostCreate(Bundle savedInstanceState) {
532 super.onPostCreate(savedInstanceState);
533 if (mLauncherCallbacks != null) {
534 mLauncherCallbacks.onPostCreate(savedInstanceState);
535 }
536 }
537
538 public boolean setLauncherCallbacks(LauncherCallbacks callbacks) {
539 mLauncherCallbacks = callbacks;
540 mLauncherCallbacks.setLauncherAppsCallback(new Launcher.LauncherAppsCallbacks() {
541 @Override
542 public void onAllAppsBoundsChanged(Rect bounds) {
543 if (LOGD) {
544 Log.d(TAG, "onAllAppsBoundsChanged(Rect): " + bounds);
545 }
546 mAppsView.setFixedBounds(bounds);
547 mWidgetsView.setFixedBounds(bounds);
548 }
549
550 @Override
551 public void dismissAllApps() {
552 if (!DISABLE_ALL_APPS_SEARCH_INTEGRATION) {
553 // Dismiss All Apps if we aren't already paused/invisible
554 if (!mPaused) {
555 showWorkspace(WorkspaceStateTransitionAnimation.SCROLL_TO_CURRENT_PAGE, true,
556 null /* onCompleteRunnable */, false /* notifyLauncherCallbacks */);
557 }
558 }
559 }
560 });
561 return true;
562 }
563
564 @Override
565 public void onLauncherProviderChange() {
566 if (mLauncherCallbacks != null) {
567 mLauncherCallbacks.onLauncherProviderChange();
568 }
569 }
570
571 /** To be overridden by subclasses to hint to Launcher that we have custom content */
572 protected boolean hasCustomContentToLeft() {
573 if (mLauncherCallbacks != null) {
574 return mLauncherCallbacks.hasCustomContentToLeft();
575 }
576 return false;
577 }
578
579 /**
580 * To be overridden by subclasses to populate the custom content container and call
581 * {@link #addToCustomContentPage}. This will only be invoked if
582 * {@link #hasCustomContentToLeft()} is {@code true}.
583 */
584 protected void populateCustomContentContainer() {
585 if (mLauncherCallbacks != null) {
586 mLauncherCallbacks.populateCustomContentContainer();
587 }
588 }
589
590 /**
591 * Invoked by subclasses to signal a change to the {@link #addCustomContentToLeft} value to
592 * ensure the custom content page is added or removed if necessary.
593 */
594 protected void invalidateHasCustomContentToLeft() {
595 if (mWorkspace == null || mWorkspace.getScreenOrder().isEmpty()) {
596 // Not bound yet, wait for bindScreens to be called.
597 return;
598 }
599
600 if (!mWorkspace.hasCustomContent() && hasCustomContentToLeft()) {
601 // Create the custom content page and call the subclass to populate it.
602 mWorkspace.createCustomContentContainer();
603 populateCustomContentContainer();
604 } else if (mWorkspace.hasCustomContent() && !hasCustomContentToLeft()) {
605 mWorkspace.removeCustomContentPage();
606 }
607 }
608
609 @Thunk void checkForLocaleChange() {
610 if (sLocaleConfiguration == null) {
611 new AsyncTask<Void, Void, LocaleConfiguration>() {
612 @Override
613 protected LocaleConfiguration doInBackground(Void... unused) {
614 LocaleConfiguration localeConfiguration = new LocaleConfiguration();
615 readConfiguration(Launcher.this, localeConfiguration);
616 return localeConfiguration;
617 }
618
619 @Override
620 protected void onPostExecute(LocaleConfiguration result) {
621 sLocaleConfiguration = result;
622 checkForLocaleChange(); // recursive, but now with a locale configuration
623 }
624 }.execute();
625 return;
626 }
627
628 final Configuration configuration = getResources().getConfiguration();
629
630 final String previousLocale = sLocaleConfiguration.locale;
631 final String locale = configuration.locale.toString();
632
633 final int previousMcc = sLocaleConfiguration.mcc;
634 final int mcc = configuration.mcc;
635
636 final int previousMnc = sLocaleConfiguration.mnc;
637 final int mnc = configuration.mnc;
638
639 boolean localeChanged = !locale.equals(previousLocale) || mcc != previousMcc || mnc != previousMn🔵
640
641 if (localeChanged) {
642 sLocaleConfiguration.locale = locale;
643 sLocaleConfiguration.mcc = mcc;
644 sLocaleConfiguration.mnc = mnc;
645
646 mIconCache.flush();
647
648 final LocaleConfiguration localeConfiguration = sLocaleConfiguration;
649 new AsyncTask<Void, Void, Void>() {
650 public Void doInBackground(Void ... args) {
651 writeConfiguration(Launcher.this, localeConfiguration);
652 return null;
653 }
654 }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void) null);
655 }
656 }
657
658 @Thunk static class LocaleConfiguration {
659 public String locale;
660 public int mcc = -1;
661 public int mnc = -1;
662 }
663
664 @Thunk static void readConfiguration(Context context, LocaleConfiguration configuration) {
665 DataInputStream in = null;
666 try {
667 in = new DataInputStream(context.openFileInput(LauncherFiles.LAUNCHER_PREFERENCES));
668 configuration.locale = in.readUTF();
669 configuration.mcc = in.readInt();
670 configuration.mnc = in.readInt();
671 } catch (FileNotFoundException e) {
672 // Ignore
673 } catch (IOException e) {
674 // Ignore
675 } finally {
676 if (in != null) {
677 try {
678 in.close();
679 } catch (IOException e) {
680 // Ignore
681 }
682 }
683 }
684 }
685
686 @Thunk static void writeConfiguration(Context context, LocaleConfiguration configuration) {
687 DataOutputStream out = null;
688 try {
689 out = new DataOutputStream(context.openFileOutput(
690 LauncherFiles.LAUNCHER_PREFERENCES, MODE_PRIVATE));
691 out.writeUTF(configuration.locale);
692 out.writeInt(configuration.mcc);
693 out.writeInt(configuration.mnc);
694 out.flush();
695 } catch (FileNotFoundException e) {
696 // Ignore
697 } catch (IOException e) {
698 //noinspection ResultOfMethodCallIgnored
699 context.getFileStreamPath(LauncherFiles.LAUNCHER_PREFERENCES).delete();
700 } finally {
701 if (out != null) {
702 try {
703 out.close();
704 } catch (IOException e) {
705 // Ignore
706 }
707 }
708 }
709 }
710
711 public Stats getStats() {
712 return mStats;
713 }
714
715 public LayoutInflater getInflater() {
716 return mInflater;
717 }
718
719 public boolean isDraggingEnabled() {
720 // We prevent dragging when we are loading the workspace as it is possible to pick up a view
721 // that is subsequently removed from the workspace in startBinding().
722 return !mModel.isLoadingWorkspace();
723 }
724
725 @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
726 public static int generateViewId() {
727 if (Build.VERSION.SDK_INT >= 17) {
728 return View.generateViewId();
729 } else {
730 // View.generateViewId() is not available. The following fallback logic is a copy
731 // of its implementation.
732 for (;;) {
733 final int result = sNextGeneratedId.get();
734 // aapt-generated IDs have the high byte nonzero; clamp to the range under that.
735 int newValue = result + 1;
736 if (newValue > 0x00FFFFFF) newValue = 1; // Roll over to 1, not 0.
737 if (sNextGeneratedId.compareAndSet(result, newValue)) {
738 return result;
739 }
740 }
741 }
742 }
743
744 public int getViewIdForItem(ItemInfo info) {
745 // This cast is safe given the > 2B range for int.
746 int itemId = (int) info.id;
747 if (mItemIdToViewId.containsKey(itemId)) {
748 return mItemIdToViewId.get(itemId);
749 }
750 int viewId = generateViewId();
751 mItemIdToViewId.put(itemId, viewId);
752 return viewId;
753 }
754
755 /**
756 * Returns whether we should delay spring loaded mode -- for shortcuts and widgets that have
757 * a configuration step, this allows the proper animations to run after other transitions.
758 */
759 private long completeAdd(PendingAddArguments args) {
760 long screenId = args.screenId;
761 if (args.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
762 // When the screen id represents an actual screen (as opposed to a rank) we make sure
763 // that the drop page actually exists.
764 screenId = ensurePendingDropLayoutExists(args.screenId);
765 }
766
767 switch (args.requestCode) {
768 case REQUEST_CREATE_SHORTCUT:
769 completeAddShortcut(args.intent, args.container, screenId, args.cellX,
770 args.cellY);
771 break;
772 case REQUEST_CREATE_APPWIDGET:
773 completeAddAppWidget(args.appWidgetId, args.container, screenId, null, null);
774 break;
775 case REQUEST_RECONFIGURE_APPWIDGET:
776 completeRestoreAppWidget(args.appWidgetId);
777 break;
778 }
779 // Before adding this resetAddInfo(), after a shortcut was added to a workspace screen,
780 // if you turned the screen off and then back while in All Apps, Launcher would not
781 // return to the workspace. Clearing mAddInfo.container here fixes this issue
782 resetAddInfo();
783 return screenId;
784 }
785
786 private void handleActivityResult(
787 final int requestCode, final int resultCode, final Intent data) {
788 // Reset the startActivity waiting flag
789 setWaitingForResult(false);
790 final int pendingAddWidgetId = mPendingAddWidgetId;
791 mPendingAddWidgetId = -1;
792
793 Runnable exitSpringLoaded = new Runnable() {
794 @Override
795 public void run() {
796 exitSpringLoadedDragModeDelayed((resultCode != RESULT_CANCELED),
797 EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null);
798 }
799 };
800
801 if (requestCode == REQUEST_BIND_APPWIDGET) {
802 final int appWidgetId = data != null ?
803 data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1) : -1;
804 if (resultCode == RESULT_CANCELED) {
805 completeTwoStageWidgetDrop(RESULT_CANCELED, appWidgetId);
806 mWorkspace.removeExtraEmptyScreenDelayed(true, exitSpringLoaded,
807 ON_ACTIVITY_RESULT_ANIMATION_DELAY, false);
808 } else if (resultCode == RESULT_OK) {
809 addAppWidgetImpl(appWidgetId, mPendingAddInfo, null,
810 mPendingAddWidgetInfo, ON_ACTIVITY_RESULT_ANIMATION_DELAY);
811 }
812 return;
813 } else if (requestCode == REQUEST_PICK_WALLPAPER) {
814 if (resultCode == RESULT_OK && mWorkspace.isInOverviewMode()) {
815 showWorkspace(false);
816 }
817 return;
818 }
819
820 boolean isWidgetDrop = (requestCode == REQUEST_PICK_APPWIDGET ||
821 requestCode == REQUEST_CREATE_APPWIDGET);
822
823 final boolean workspaceLocked = isWorkspaceLocked();
824 // We have special handling for widgets
825 if (isWidgetDrop) {
826 final int appWidgetId;
827 int widgetId = data != null ? data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1)
828 : -1;
829 if (widgetId < 0) {
830 appWidgetId = pendingAddWidgetId;
831 } else {
832 appWidgetId = widgetId;
833 }
834
835 final int result;
836 if (appWidgetId < 0 || resultCode == RESULT_CANCELED) {
837 Log.e(TAG, "Error: appWidgetId (EXTRA_APPWIDGET_ID) was not " +
838 "returned from the widget configuration activity.");
839 result = RESULT_CANCELED;
840 completeTwoStageWidgetDrop(result, appWidgetId);
841 final Runnable onComplete = new Runnable() {
842 @Override
843 public void run() {
844 exitSpringLoadedDragModeDelayed(false, 0, null);
845 }
846 };
847 if (workspaceLocked) {
848 // No need to remove the empty screen if we're mid-binding, as the
849 // the bind will not add the empty screen.
850 mWorkspace.postDelayed(onComplete, ON_ACTIVITY_RESULT_ANIMATION_DELAY);
851 } else {
852 mWorkspace.removeExtraEmptyScreenDelayed(true, onComplete,
853 ON_ACTIVITY_RESULT_ANIMATION_DELAY, false);
854 }
855 } else {
856 if (!workspaceLocked) {
857 if (mPendingAddInfo.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
858 // When the screen id represents an actual screen (as opposed to a rank)
859 // we make sure that the drop page actually exists.
860 mPendingAddInfo.screenId =
861 ensurePendingDropLayoutExists(mPendingAddInfo.screenId);
862 }
863 final CellLayout dropLayout = mWorkspace.getScreenWithId(mPendingAddInfo.screenId);
864
865 dropLayout.setDropPending(true);
866 final Runnable onComplete = new Runnable() {
867 @Override
868 public void run() {
869 completeTwoStageWidgetDrop(resultCode, appWidgetId);
870 dropLayout.setDropPending(false);
871 }
872 };
873 mWorkspace.removeExtraEmptyScreenDelayed(true, onComplete,
874 ON_ACTIVITY_RESULT_ANIMATION_DELAY, false);
875 } else {
876 PendingAddArguments args = preparePendingAddArgs(requestCode, data, appWidgetId,
877 mPendingAddInfo);
878 sPendingAddItem = args;
879 }
880 }
881 return;
882 }
883
884 if (requestCode == REQUEST_RECONFIGURE_APPWIDGET) {
885 if (resultCode == RESULT_OK) {
886 // Update the widget view.
887 PendingAddArguments args = preparePendingAddArgs(requestCode, data,
888 pendingAddWidgetId, mPendingAddInfo);
889 if (workspaceLocked) {
890 sPendingAddItem = args;
891 } else {
892 completeAdd(args);
893 }
894 }
895 // Leave the widget in the pending state if the user canceled the configure.
896 return;
897 }
898
899 // The pattern used here is that a user PICKs a specific application,
900 // which, depending on the target, might need to CREATE the actual target.
901
902 // For example, the user would PICK_SHORTCUT for "Music playlist", and we
903 // launch over to the Music app to actually CREATE_SHORTCUT.
904 if (resultCode == RESULT_OK && mPendingAddInfo.container != ItemInfo.NO_ID) {
905 final PendingAddArguments args = preparePendingAddArgs(requestCode, data, -1,
906 mPendingAddInfo);
907 if (isWorkspaceLocked()) {
908 sPendingAddItem = args;
909 } else {
910 completeAdd(args);
911 mWorkspace.removeExtraEmptyScreenDelayed(true, exitSpringLoaded,
912 ON_ACTIVITY_RESULT_ANIMATION_DELAY, false);
913 }
914 } else if (resultCode == RESULT_CANCELED) {
915 mWorkspace.removeExtraEmptyScreenDelayed(true, exitSpringLoaded,
916 ON_ACTIVITY_RESULT_ANIMATION_DELAY, false);
917 }
918 mDragLayer.clearAnimatedView();
919
920 }
921
922 @Override
923 protected void onActivityResult(
924 final int requestCode, final int resultCode, final Intent data) {
925 handleActivityResult(requestCode, resultCode, data);
926 if (mLauncherCallbacks != null) {
927 mLauncherCallbacks.onActivityResult(requestCode, resultCode, data);
928 }
929 }
930
931 private PendingAddArguments preparePendingAddArgs(int requestCode, Intent data, int
932 appWidgetId, ItemInfo info) {
933 PendingAddArguments args = new PendingAddArguments();
934 args.requestCode = requestCode;
935 args.intent = data;
936 args.container = info.container;
937 args.screenId = info.screenId;
938 args.cellX = info.cellX;
939 args.cellY = info.cellY;
940 args.appWidgetId = appWidgetId;
941 return args;
942 }
943
944 /**
945 * Check to see if a given screen id exists. If not, create it at the end, return the new id.
946 *
947 * @param screenId the screen id to check
948 * @return the new screen, or screenId if it exists
949 */
950 private long ensurePendingDropLayoutExists(long screenId) {
951 CellLayout dropLayout =
952 (CellLayout) mWorkspace.getScreenWithId(screenId);
953 if (dropLayout == null) {
954 // it's possible that the add screen was removed because it was
955 // empty and a re-bind occurred
956 mWorkspace.addExtraEmptyScreen();
957 return mWorkspace.commitExtraEmptyScreen();
958 } else {
959 return screenId;
960 }
961 }
962
963 @Thunk void completeTwoStageWidgetDrop(final int resultCode, final int appWidgetId) {
964 CellLayout cellLayout =
965 (CellLayout) mWorkspace.getScreenWithId(mPendingAddInfo.screenId);
966 Runnable onCompleteRunnable = null;
967 int animationType = 0;
968
969 AppWidgetHostView boundWidget = null;
970 if (resultCode == RESULT_OK) {
971 animationType = Workspace.COMPLETE_TWO_STAGE_WIDGET_DROP_ANIMATION;
972 final AppWidgetHostView layout = mAppWidgetHost.createView(this, appWidgetId,
973 mPendingAddWidgetInfo);
974 boundWidget = layout;
975 onCompleteRunnable = new Runnable() {
976 @Override
977 public void run() {
978 completeAddAppWidget(appWidgetId, mPendingAddInfo.container,
979 mPendingAddInfo.screenId, layout, null);
980 exitSpringLoadedDragModeDelayed((resultCode != RESULT_CANCELED),
981 EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null);
982 }
983 };
984 } else if (resultCode == RESULT_CANCELED) {
985 mAppWidgetHost.deleteAppWidgetId(appWidgetId);
986 animationType = Workspace.CANCEL_TWO_STAGE_WIDGET_DROP_ANIMATION;
987 }
988 if (mDragLayer.getAnimatedView() != null) {
989 mWorkspace.animateWidgetDrop(mPendingAddInfo, cellLayout,
990 (DragView) mDragLayer.getAnimatedView(), onCompleteRunnable,
991 animationType, boundWidget, true);
992 } else if (onCompleteRunnable != null) {
993 // The animated view may be null in the case of a rotation during widget configuration
994 onCompleteRunnable.run();
995 }
996 }
997
998 @Override
999 protected void onStop() {
1000 super.onStop();
1001 FirstFrameAnimatorHelper.setIsVisible(false);
1002
1003 if (mLauncherCallbacks != null) {
1004 mLauncherCallbacks.onStop();
1005 }
1006 }
1007
1008 @Override
1009 protected void onStart() {
1010 super.onStart();
1011 FirstFrameAnimatorHelper.setIsVisible(true);
1012
1013 if (mLauncherCallbacks != null) {
1014 mLauncherCallbacks.onStart();
1015 }
1016 }
1017
1018 @Override
1019 protected void onResume() {
1020 long startTime = 0;
1021 if (DEBUG_RESUME_TIME) {
1022 startTime = System.currentTimeMillis();
1023 Log.v(TAG, "Launcher.onResume()");
1024 }
1025
1026 if (mLauncherCallbacks != null) {
1027 mLauncherCallbacks.preOnResume();
1028 }
1029
1030 super.onResume();
1031
1032 // Restore the previous launcher state
1033 if (mOnResumeState == State.WORKSPACE) {
1034 showWorkspace(false);
1035 } else if (mOnResumeState == State.APPS) {
1036 boolean launchedFromApp = (mWaitingForResume != null);
1037 // Don't update the predicted apps if the user is returning to launcher in the apps
1038 // view after launching an app, as they may be depending on the UI to be static to
1039 // switch to another app, otherwise, if it was
1040 showAppsView(false /* animated */, false /* resetListToTop */,
1041 !launchedFromApp /* updatePredictedApps */);
1042 } else if (mOnResumeState == State.WIDGETS) {
1043 showWidgetsView(false, false);
1044 }
1045 mOnResumeState = State.NONE;
1046
1047 // Restore the apps state if we are in all apps
1048 if (!Launcher.DISABLE_ALL_APPS_SEARCH_INTEGRATION) {
1049 // Otherwise, notify the callbacks if we are in all apps mode
1050 if (mState == State.APPS) {
1051 if (mLauncherCallbacks != null) {
1052 mLauncherCallbacks.onAllAppsShown();
1053 }
1054 }
1055 }
1056
1057 // Background was set to gradient in onPause(), restore to transparent if in all apps.
1058 setWorkspaceBackground(mState == State.WORKSPACE ? WORKSPACE_BACKGROUND_TRANSPARENT
1059 : WORKSPACE_BACKGROUND_GRADIENT);
1060
1061 mPaused = false;
1062 if (mRestoring || mOnResumeNeedsLoad) {
1063 setWorkspaceLoading(true);
1064 mModel.startLoader(PagedView.INVALID_RESTORE_PAGE);
1065 mRestoring = false;
1066 mOnResumeNeedsLoad = false;
1067 }
1068 if (mBindOnResumeCallbacks.size() > 0) {
1069 // We might have postponed some bind calls until onResume (see waitUntilResume) --
1070 // execute them here
1071 long startTimeCallbacks = 0;
1072 if (DEBUG_RESUME_TIME) {
1073 startTimeCallbacks = System.currentTimeMillis();
1074 }
1075
1076 for (int i = 0; i < mBindOnResumeCallbacks.size(); i++) {
1077 mBindOnResumeCallbacks.get(i).run();
1078 }
1079 mBindOnResumeCallbacks.clear();
1080 if (DEBUG_RESUME_TIME) {
1081 Log.d(TAG, "Time spent processing callbacks in onResume: " +
1082 (System.currentTimeMillis() - startTimeCallbacks));
1083 }
1084 }
1085 if (mOnResumeCallbacks.size() > 0) {
1086 for (int i = 0; i < mOnResumeCallbacks.size(); i++) {
1087 mOnResumeCallbacks.get(i).run();
1088 }
1089 mOnResumeCallbacks.clear();
1090 }
1091
1092 // Reset the pressed state of icons that were locked in the press state while activities
1093 // were launching
1094 if (mWaitingForResume != null) {
1095 // Resets the previous workspace icon press state
1096 mWaitingForResume.setStayPressed(false);
1097 }
1098
1099 // It is possible that widgets can receive updates while launcher is not in the foreground.
1100 // Consequently, the widgets will be inflated in the orientation of the foreground activity
1101 // (framework issue). On resuming, we ensure that any widgets are inflated for the current
1102 // orientation.
1103 getWorkspace().reinflateWidgetsIfNecessary();
1104 reinflateQSBIfNecessary();
1105
1106 if (DEBUG_RESUME_TIME) {
1107 Log.d(TAG, "Time spent in onResume: " + (System.currentTimeMillis() - startTime));
1108 }
1109
1110 if (mWorkspace.getCustomContentCallbacks() != null) {
1111 // If we are resuming and the custom content is the current page, we call onShow().
1112 // It is also poassible that onShow will instead be called slightly after first layout
1113 // if PagedView#setRestorePage was set to the custom content page in onCreate().
1114 if (mWorkspace.isOnOrMovingToCustomContent()) {
1115 mWorkspace.getCustomContentCallbacks().onShow(true);
1116 }
1117 }
1118 updateInteraction(Workspace.State.NORMAL, mWorkspace.getState());
1119 mWorkspace.onResume();
1120
1121 if (!isWorkspaceLoading()) {
1122 // Process any items that were added while Launcher was away.
1123 InstallShortcutReceiver.disableAndFlushInstallQueue(this);
1124 }
1125
1126 if (mLauncherCallbacks != null) {
1127 mLauncherCallbacks.onResume();
1128 }
1129 }
1130
1131 @Override
1132 protected void onPause() {
1133 // Ensure that items added to Launcher are queued until Launcher returns
1134 InstallShortcutReceiver.enableInstallQueue();
1135
1136 super.onPause();
1137 mPaused = true;
1138 mDragController.cancelDrag();
1139 mDragController.resetLastGestureUpTime();
1140
1141 // We call onHide() aggressively. The custom content callbacks should be able to
1142 // debounce excess onHide calls.
1143 if (mWorkspace.getCustomContentCallbacks() != null) {
1144 mWorkspace.getCustomContentCallbacks().onHide();
1145 }
1146
1147 if (mLauncherCallbacks != null) {
1148 mLauncherCallbacks.onPause();
1149 }
1150 }
1151
1152 public interface CustomContentCallbacks {
1153 // Custom content is completely shown. {@code fromResume} indicates whether this was caused
1154 // by a onResume or by scrolling otherwise.
1155 public void onShow(boolean fromResume);
1156
1157 // Custom content is completely hidden
1158 public void onHide();
1159
1160 // Custom content scroll progress changed. From 0 (not showing) to 1 (fully showing).
1161 public void onScrollProgressChanged(float progress);
1162
1163 // Indicates whether the user is allowed to scroll away from the custom content.
1164 boolean isScrollingAllowed();
1165 }
1166
1167 public interface LauncherOverlay {
1168
1169 /**
1170 * Touch interaction leading to overscroll has begun
1171 */
1172 public void onScrollInteractionBegin();
1173
1174 /**
1175 * Touch interaction related to overscroll has ended
1176 */
1177 public void onScrollInteractionEnd();
1178
1179 /**
1180 * Scroll progress, between 0 and 100, when the user scrolls beyond the leftmost
1181 * screen (or in the case of RTL, the rightmost screen).
1182 */
1183 public void onScrollChange(int progress, boolean rtl);
1184
1185 /**
1186 * Screen has stopped scrolling
1187 */
1188 public void onScrollSettled();
1189
1190 /**
1191 * This method can be called by the Launcher in order to force the LauncherOverlay
1192 * to exit fully immersive mode.
1193 */
1194 public void forceExitFullImmersion();
1195 }
1196
1197 public interface LauncherAppsCallbacks {
1198 /**
1199 * Updates launcher to the available space that AllApps can take so as not to overlap with
1200 * any other views.
1201 */
1202 public void onAllAppsBoundsChanged(Rect bounds);
1203
1204 /**
1205 * Called to dismiss all apps if it is showing.
1206 */
1207 public void dismissAllApps();
1208 }
1209
1210 public interface LauncherOverlayCallbacks {
1211 /**
1212 * This method indicates whether a call to {@link #enterFullImmersion()} will succeed,
1213 * however it doesn't modify any state within the launcher.
1214 */
1215 public boolean canEnterFullImmersion();
1216
1217 /**
1218 * Should be called to tell Launcher that the LauncherOverlay will take over interaction,
1219 * eg. by occupying the full screen and handling all touch events.
1220 *
1221 * @return true if Launcher allows the LauncherOverlay to become fully immersive. In this
1222 * case, Launcher will modify any necessary state and assumes the overlay is
1223 * handling all interaction. If false, the LauncherOverlay should cancel any
1224 *
1225 */
1226 public boolean enterFullImmersion();
1227
1228 /**
1229 * Must be called when exiting fully immersive mode. Indicates to Launcher that it has
1230 * full control over UI and state.
1231 */
1232 public void exitFullImmersion();
1233 }
1234
1235 class LauncherOverlayCallbacksImpl implements LauncherOverlayCallbacks {
1236
1237 @Override
1238 public boolean canEnterFullImmersion() {
1239 return mState == State.WORKSPACE;
1240 }
1241
1242 @Override
1243 public boolean enterFullImmersion() {
1244 if (mState == State.WORKSPACE) {
1245 // When fully immersed, disregard any touches which fall through.
1246 mDragLayer.setBlockTouch(true);
1247 return true;
1248 }
1249 return false;
1250 }
1251
1252 @Override
1253 public void exitFullImmersion() {
1254 mDragLayer.setBlockTouch(false);
1255 }
1256 }
1257
1258 protected boolean hasSettings() {
1259 if (mLauncherCallbacks != null) {
1260 return mLauncherCallbacks.hasSettings();
1261 }
1262 return false;
1263 }
1264
1265 public void addToCustomContentPage(View customContent,
1266 CustomContentCallbacks callbacks, String description) {
1267 mWorkspace.addToCustomContentPage(customContent, callbacks, description);
1268 }
1269
1270 // The custom content needs to offset its content to account for the QSB
1271 public int getTopOffsetForCustomContent() {
1272 return mWorkspace.getPaddingTop();
1273 }
1274
1275 @Override
1276 public Object onRetainNonConfigurationInstance() {
1277 // Flag the loader to stop early before switching
1278 if (mModel.isCurrentCallbacks(this)) {
1279 mModel.stopLoader();
1280 }
1281 //TODO(hyunyoungs): stop the widgets loader when there is a rotation.
1282
1283 return Boolean.TRUE;
1284 }
1285
1286 // We can't hide the IME if it was forced open. So don't bother
1287 @Override
1288 public void onWindowFocusChanged(boolean hasFocus) {
1289 super.onWindowFocusChanged(hasFocus);
1290 mHasFocus = hasFocus;
1291
1292 if (mLauncherCallbacks != null) {
1293 mLauncherCallbacks.onWindowFocusChanged(hasFocus);
1294 }
1295 }
1296
1297 private boolean acceptFilter() {
1298 final InputMethodManager inputManager = (InputMethodManager)
1299 getSystemService(Context.INPUT_METHOD_SERVICE);
1300 return !inputManager.isFullscreenMode();
1301 }
1302
1303 @Override
1304 public boolean onKeyDown(int keyCode, KeyEvent event) {
1305 final int uniChar = event.getUnicodeChar();
1306 final boolean handled = super.onKeyDown(keyCode, event);
1307 final boolean isKeyNotWhitespace = uniChar > 0 && !Character.isWhitespace(uniChar);
1308 if (!handled && acceptFilter() && isKeyNotWhitespace) {
1309 boolean gotKey = TextKeyListener.getInstance().onKeyDown(mWorkspace, mDefaultKeySsb,
1310 keyCode, event);
1311 if (gotKey && mDefaultKeySsb != null && mDefaultKeySsb.length() > 0) {
1312 // something usable has been typed - start a search
1313 // the typed text will be retrieved and cleared by
1314 // showSearchDialog()
1315 // If there are multiple keystrokes before the search dialog takes focus,
1316 // onSearchRequested() will be called for every keystroke,
1317 // but it is idempotent, so it's fine.
1318 return onSearchRequested();
1319 }
1320 }
1321
1322 // Eat the long press event so the keyboard doesn't come up.
1323 if (keyCode == KeyEvent.KEYCODE_MENU && event.isLongPress()) {
1324 return true;
1325 }
1326
1327 return handled;
1328 }
1329
1330 private String getTypedText() {
1331 return mDefaultKeySsb.toString();
1332 }
1333
1334 private void clearTypedText() {
1335 mDefaultKeySsb.clear();
1336 mDefaultKeySsb.clearSpans();
1337 Selection.setSelection(mDefaultKeySsb, 0);
1338 }
1339
1340 /**
1341 * Given the integer (ordinal) value of a State enum instance, convert it to a variable of type
1342 * State
1343 */
1344 private static State intToState(int stateOrdinal) {
1345 State state = State.WORKSPACE;
1346 final State[] stateValues = State.values();
1347 for (int i = 0; i < stateValues.length; i++) {
1348 if (stateValues[i].ordinal() == stateOrdinal) {
1349 state = stateValues[i];
1350 break;
1351 }
1352 }
1353 return state;
1354 }
1355
1356 /**
1357 * Restores the previous state, if it exists.
1358 *
1359 * @param savedState The previous state.
1360 */
1361 @SuppressWarnings("unchecked")
1362 private void restoreState(Bundle savedState) {
1363 if (savedState == null) {
1364 return;
1365 }
1366
1367 State state = intToState(savedState.getInt(RUNTIME_STATE, State.WORKSPACE.ordinal()));
1368 if (state == State.APPS || state == State.WIDGETS) {
1369 mOnResumeState = state;
1370 }
1371
1372 int currentScreen = savedState.getInt(RUNTIME_STATE_CURRENT_SCREEN,
1373 PagedView.INVALID_RESTORE_PAGE);
1374 if (currentScreen != PagedView.INVALID_RESTORE_PAGE) {
1375 mWorkspace.setRestorePage(currentScreen);
1376 }
1377
1378 final long pendingAddContainer = savedState.getLong(RUNTIME_STATE_PENDING_ADD_CONTAINER, -1);
1379 final long pendingAddScreen = savedState.getLong(RUNTIME_STATE_PENDING_ADD_SCREEN, -1);
1380
1381 if (pendingAddContainer != ItemInfo.NO_ID && pendingAddScreen > -1) {
1382 mPendingAddInfo.container = pendingAddContainer;
1383 mPendingAddInfo.screenId = pendingAddScreen;
1384 mPendingAddInfo.cellX = savedState.getInt(RUNTIME_STATE_PENDING_ADD_CELL_X);
1385 mPendingAddInfo.cellY = savedState.getInt(RUNTIME_STATE_PENDING_ADD_CELL_Y);
1386 mPendingAddInfo.spanX = savedState.getInt(RUNTIME_STATE_PENDING_ADD_SPAN_X);
1387 mPendingAddInfo.spanY = savedState.getInt(RUNTIME_STATE_PENDING_ADD_SPAN_Y);
1388 AppWidgetProviderInfo info = savedState.getParcelable(
1389 RUNTIME_STATE_PENDING_ADD_WIDGET_INFO);
1390 mPendingAddWidgetInfo = info == null ?
1391 null : LauncherAppWidgetProviderInfo.fromProviderInfo(this, info);
1392
1393 mPendingAddWidgetId = savedState.getInt(RUNTIME_STATE_PENDING_ADD_WIDGET_ID);
1394 setWaitingForResult(true);
1395 mRestoring = true;
1396 }
1397
1398 mItemIdToViewId = (HashMap<Integer, Integer>)
1399 savedState.getSerializable(RUNTIME_STATE_VIEW_IDS);
1400 }
1401
1402 /**
1403 * Finds all the views we need and configure them properly.
1404 */
1405 private void setupViews() {
1406 final DragController dragController = mDragController;
1407
1408 mLauncherView = findViewById(R.id.launcher);
1409 mFocusHandler = (FocusIndicatorView) findViewById(R.id.focus_indicator);
1410 mDragLayer = (DragLayer) findViewById(R.id.drag_layer);
1411 mWorkspace = (Workspace) mDragLayer.findViewById(R.id.workspace);
1412 mWorkspace.setPageSwitchListener(this);
1413 mPageIndicators = mDragLayer.findViewById(R.id.page_indicator);
1414
1415 mLauncherView.setSystemUiVisibility(
1416 View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
1417 mWorkspaceBackgroundDrawable = getResources().getDrawable(R.drawable.workspace_bg);
1418
1419 // Setup the drag layer
1420 mDragLayer.setup(this, dragController);
1421
1422 // Setup the hotseat
1423 mHotseat = (Hotseat) findViewById(R.id.hotseat);
1424 if (mHotseat != null) {
1425 mHotseat.setOnLongClickListener(this);
1426 }
1427
1428 mOverviewPanel = (ViewGroup) findViewById(R.id.overview_panel);
1429 View widgetButton = findViewById(R.id.widget_button);
1430 widgetButton.setOnClickListener(new OnClickListener() {
1431 @Override
1432 public void onClick(View arg0) {
1433 if (!mWorkspace.isSwitchingState()) {
1434 onClickAddWidgetButton(arg0);
1435 }
1436 }
1437 });
1438 widgetButton.setOnTouchListener(getHapticFeedbackTouchListener());
1439
1440 View wallpaperButton = findViewById(R.id.wallpaper_button);
1441 wallpaperButton.setOnClickListener(new OnClickListener() {
1442 @Override
1443 public void onClick(View arg0) {
1444 if (!mWorkspace.isSwitchingState()) {
1445 onClickWallpaperPicker(arg0);
1446 }
1447 }
1448 });
1449 wallpaperButton.setOnTouchListener(getHapticFeedbackTouchListener());
1450
1451 View settingsButton = findViewById(R.id.settings_button);
1452 if (hasSettings()) {
1453 settingsButton.setOnClickListener(new OnClickListener() {
1454 @Override
1455 public void onClick(View arg0) {
1456 if (!mWorkspace.isSwitchingState()) {
1457 onClickSettingsButton(arg0);
1458 }
1459 }
1460 });
1461 settingsButton.setOnTouchListener(getHapticFeedbackTouchListener());
1462 } else {
1463 settingsButton.setVisibility(View.GONE);
1464 }
1465
1466 mOverviewPanel.setAlpha(0f);
1467
1468 // Setup the workspace
1469 mWorkspace.setHapticFeedbackEnabled(false);
1470 mWorkspace.setOnLongClickListener(this);
1471 mWorkspace.setup(dragController);
1472 dragController.addDragListener(mWorkspace);
1473
1474 // Get the search/delete bar
1475 mSearchDropTargetBar = (SearchDropTargetBar)
1476 mDragLayer.findViewById(R.id.search_drop_target_bar);
1477
1478 // Setup Apps
1479 mAppsView = (AllAppsContainerView) findViewById(R.id.apps_view);
1480 if (isAllAppsSearchOverridden()) {
1481 mAppsView.hideHeaderBar();
1482 }
1483
1484 // Setup AppsCustomize
1485 mWidgetsView = (WidgetsContainerView) findViewById(R.id.widgets_view);
1486
1487 // Setup the drag controller (drop targets have to be added in reverse order in priority)
1488 dragController.setDragScoller(mWorkspace);
1489 dragController.setScrollView(mDragLayer);
1490 dragController.setMoveTarget(mWorkspace);
1491 dragController.addDropTarget(mWorkspace);
1492 if (mSearchDropTargetBar != null) {
1493 mSearchDropTargetBar.setup(this, dragController);
1494 mSearchDropTargetBar.setQsbSearchBar(getOrCreateQsbBar());
1495 }
1496
1497 if (getResources().getBoolean(R.bool.debug_memory_enabled)) {
1498 Log.v(TAG, "adding WeightWatcher");
1499 mWeightWatcher = new WeightWatcher(this);
1500 mWeightWatcher.setAlpha(0.5f);
1501 ((FrameLayout) mLauncherView).addView(mWeightWatcher,
1502 new FrameLayout.LayoutParams(
1503 FrameLayout.LayoutParams.MATCH_PARENT,
1504 FrameLayout.LayoutParams.WRAP_CONTENT,
1505 Gravity.BOTTOM)
1506 );
1507
1508 boolean show = shouldShowWeightWatcher();
1509 mWeightWatcher.setVisibility(show ? View.VISIBLE : View.GONE);
1510 }
1511 }
1512
1513 /**
1514 * Sets the all apps button. This method is called from {@link Hotseat}.
1515 */
1516 public void setAllAppsButton(View allAppsButton) {
1517 mAllAppsButton = allAppsButton;
1518 }
1519
1520 public View getAllAppsButton() {
1521 return mAllAppsButton;
1522 }
1523
1524 /**
1525 * Creates a view representing a shortcut.
1526 *
1527 * @param info The data structure describing the shortcut.
1528 */
1529 View createShortcut(ShortcutInfo info) {
1530 return createShortcut((ViewGroup) mWorkspace.getChildAt(mWorkspace.getCurrentPage()), info);
1531 }
1532
1533 /**
1534 * Creates a view representing a shortcut inflated from the specified resource.
1535 *
1536 * @param parent The group the shortcut belongs to.
1537 * @param info The data structure describing the shortcut.
1538 *
1539 * @return A View inflated from layoutResId.
1540 */
1541 public View createShortcut(ViewGroup parent, ShortcutInfo info) {
1542 BubbleTextView favorite = (BubbleTextView) mInflater.inflate(R.layout.app_icon,
1543 parent, false);
1544 favorite.applyFromShortcutInfo(info, mIconCache);
1545 favorite.setCompoundDrawablePadding(mDeviceProfile.iconDrawablePaddingPx);
1546 favorite.setOnClickListener(this);
1547 favorite.setOnFocusChangeListener(mFocusHandler);
1548 return favorite;
1549 }
1550
1551 /**
1552 * Add a shortcut to the workspace.
1553 *
1554 * @param data The intent describing the shortcut.
1555 * @param cellInfo The position on screen where to create the shortcut.
1556 */
1557 private void completeAddShortcut(Intent data, long container, long screenId, int cellX,
1558 int cellY) {
1559 int[] cellXY = mTmpAddItemCellCoordinates;
1560 int[] touchXY = mPendingAddInfo.dropPos;
1561 CellLayout layout = getCellLayout(container, screenId);
1562
1563 ShortcutInfo info = InstallShortcutReceiver.fromShortcutIntent(this, data);
1564 if (info == null) {
1565 return;
1566 }
1567 final View view = createShortcut(info);
1568
1569 boolean foundCellSpan = false;
1570 // First we check if we already know the exact location where we want to add this item.
1571 if (cellX >= 0 && cellY >= 0) {
1572 cellXY[0] = cellX;
1573 cellXY[1] = cellY;
1574 foundCellSpan = true;
1575
1576 // If appropriate, either create a folder or add to an existing folder
1577 if (mWorkspace.createUserFolderIfNecessary(view, container, layout, cellXY, 0,
1578 true, null,null)) {
1579 return;
1580 }
1581 DragObject dragObject = new DragObject();
1582 dragObject.dragInfo = info;
1583 if (mWorkspace.addToExistingFolderIfNecessary(view, layout, cellXY, 0, dragObject,
1584 true)) {
1585 return;
1586 }
1587 } else if (touchXY != null) {
1588 // when dragging and dropping, just find the closest free spot
1589 int[] result = layout.findNearestVacantArea(touchXY[0], touchXY[1], 1, 1, cellXY);
1590 foundCellSpan = (result != null);
1591 } else {
1592 foundCellSpan = layout.findCellForSpan(cellXY, 1, 1);
1593 }
1594
1595 if (!foundCellSpan) {
1596 showOutOfSpaceMessage(isHotseatLayout(layout));
1597 return;
1598 }
1599
1600 LauncherModel.addItemToDatabase(this, info, container, screenId, cellXY[0], cellXY[1]);
1601
1602 if (!mRestoring) {
1603 mWorkspace.addInScreen(view, container, screenId, cellXY[0], cellXY[1], 1, 1,
1604 isWorkspaceLocked());
1605 }
1606 }
1607
1608 private int[] getSpanForWidget(ComponentName component, int minWidth, int minHeight) {
1609 Rect padding = AppWidgetHostView.getDefaultPaddingForWidget(this, component, null);
1610 // We want to account for the extra amount of padding that we are adding to the widget
1611 // to ensure that it gets the full amount of space that it has requested
1612 int requiredWidth = minWidth + padding.left + padding.right;
1613 int requiredHeight = minHeight + padding.top + padding.bottom;
1614 return CellLayout.rectToCell(this, requiredWidth, requiredHeight, null);
1615 }
1616
1617 public int[] getSpanForWidget(AppWidgetProviderInfo info) {
1618 return getSpanForWidget(info.provider, info.minWidth, info.minHeight);
1619 }
1620
1621 public int[] getMinSpanForWidget(AppWidgetProviderInfo info) {
1622 return getSpanForWidget(info.provider, info.minResizeWidth, info.minResizeHeight);
1623 }
1624
1625 /**
1626 * Add a widget to the workspace.
1627 *
1628 * @param appWidgetId The app widget id
1629 */
1630 @Thunk void completeAddAppWidget(int appWidgetId, long container, long screenId,
1631 AppWidgetHostView hostView, LauncherAppWidgetProviderInfo appWidgetInfo) {
1632
1633 ItemInfo info = mPendingAddInfo;
1634 if (appWidgetInfo == null) {
1635 appWidgetInfo = LauncherAppWidgetProviderInfo.fromProviderInfo(this,
1636 mAppWidgetManager.getAppWidgetInfo(appWidgetId));
1637 }
1638
1639 if (appWidgetInfo.isCustomWidget) {
1640 appWidgetId = LauncherAppWidgetInfo.CUSTOM_WIDGET_ID;
1641 }
1642
1643 LauncherAppWidgetInfo launcherInfo;
1644 launcherInfo = new LauncherAppWidgetInfo(appWidgetId, appWidgetInfo.provider);
1645 launcherInfo.spanX = info.spanX;
1646 launcherInfo.spanY = info.spanY;
1647 launcherInfo.minSpanX = info.minSpanX;
1648 launcherInfo.minSpanY = info.minSpanY;
1649 launcherInfo.user = mAppWidgetManager.getUser(appWidgetInfo);
1650
1651 LauncherModel.addItemToDatabase(this, launcherInfo,
1652 container, screenId, info.cellX, info.cellY);
1653
1654 if (!mRestoring) {
1655 if (hostView == null) {
1656 // Perform actual inflation because we're live
1657 launcherInfo.hostView = mAppWidgetHost.createView(this, appWidgetId,
1658 appWidgetInfo);
1659 } else {
1660 // The AppWidgetHostView has already been inflated and instantiated
1661 launcherInfo.hostView = hostView;
1662 }
1663 launcherInfo.hostView.setTag(launcherInfo);
1664 launcherInfo.hostView.setVisibility(View.VISIBLE);
1665 launcherInfo.notifyWidgetSizeChanged(this);
1666
1667 mWorkspace.addInScreen(launcherInfo.hostView, container, screenId, info.cellX,
1668 info.cellY, launcherInfo.spanX, launcherInfo.spanY, isWorkspaceLocked());
1669
1670 addWidgetToAutoAdvanceIfNeeded(launcherInfo.hostView, appWidgetInfo);
1671 }
1672 resetAddInfo();
1673 }
1674
1675 private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
1676 @Override
1677 public void onReceive(Context context, Intent intent) {
1678 final String action = intent.getAction();
1679 if (Intent.ACTION_SCREEN_OFF.equals(action)) {
1680 mUserPresent = false;
1681 mDragLayer.clearAllResizeFrames();
1682 updateAutoAdvanceState();
1683
1684 // Reset AllApps to its initial state only if we are not in the middle of
1685 // processing a multi-step drop
1686 if (mAppsView != null && mWidgetsView != null &&
1687 mPendingAddInfo.container == ItemInfo.NO_ID) {
1688 showWorkspace(false);
1689 }
1690 } else if (Intent.ACTION_USER_PRESENT.equals(action)) {
1691 mUserPresent = true;
1692 updateAutoAdvanceState();
1693 } else if (ENABLE_DEBUG_INTENTS && DebugIntents.DELETE_DATABASE.equals(action)) {
1694 mModel.resetLoadedState(false, true);
1695 mModel.startLoader(PagedView.INVALID_RESTORE_PAGE,
1696 LauncherModel.LOADER_FLAG_CLEAR_WORKSPACE);
1697 } else if (ENABLE_DEBUG_INTENTS && DebugIntents.MIGRATE_DATABASE.equals(action)) {
1698 mModel.resetLoadedState(false, true);
1699 mModel.startLoader(PagedView.INVALID_RESTORE_PAGE,
1700 LauncherModel.LOADER_FLAG_CLEAR_WORKSPACE
1701 | LauncherModel.LOADER_FLAG_MIGRATE_SHORTCUTS);
1702 }
1703 }
1704 };
1705
1706 @Override
1707 public void onAttachedToWindow() {
1708 super.onAttachedToWindow();
1709
1710 // Listen for broadcasts related to user-presence
1711 final IntentFilter filter = new IntentFilter();
1712 filter.addAction(Intent.ACTION_SCREEN_OFF);
1713 filter.addAction(Intent.ACTION_USER_PRESENT);
1714 // For handling managed profiles
1715 if (ENABLE_DEBUG_INTENTS) {
1716 filter.addAction(DebugIntents.DELETE_DATABASE);
1717 filter.addAction(DebugIntents.MIGRATE_DATABASE);
1718 }
1719 registerReceiver(mReceiver, filter);
1720 FirstFrameAnimatorHelper.initializeDrawListener(getWindow().getDecorView());
1721 setupTransparentSystemBarsForLmp();
1722 mAttached = true;
1723 mVisible = true;
1724 }
1725
1726 /**
1727 * Sets up transparent navigation and status bars in LMP.
1728 * This method is a no-op for other platform versions.
1729 */
1730 @TargetApi(Build.VERSION_CODES.LOLLIPOP)
1731 private void setupTransparentSystemBarsForLmp() {
1732 if (Utilities.isLmpOrAbove()) {
1733 Window window = getWindow();
1734 window.getAttributes().systemUiVisibility |=
1735 (View.SYSTEM_UI_FLAG_LAYOUT_STABLE
1736 | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
1737 | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
1738 window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS
1739 | WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
1740 window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
1741 window.setStatusBarColor(Color.TRANSPARENT);
1742 window.setNavigationBarColor(Color.TRANSPARENT);
1743 }
1744 }
1745
1746 @Override
1747 public void onDetachedFromWindow() {
1748 super.onDetachedFromWindow();
1749 mVisible = false;
1750
1751 if (mAttached) {
1752 unregisterReceiver(mReceiver);
1753 mAttached = false;
1754 }
1755 updateAutoAdvanceState();
1756 }
1757
1758 public void onWindowVisibilityChanged(int visibility) {
1759 mVisible = visibility == View.VISIBLE;
1760 updateAutoAdvanceState();
1761 // The following code used to be in onResume, but it turns out onResume is called when
1762 // you're in All Apps and click home to go to the workspace. onWindowVisibilityChanged
1763 // is a more appropriate event to handle
1764 if (mVisible) {
1765 if (!mWorkspaceLoading) {
1766 final ViewTreeObserver observer = mWorkspace.getViewTreeObserver();
1767 // We want to let Launcher draw itself at least once before we force it to build
1768 // layers on all the workspace pages, so that transitioning to Launcher from other
1769 // apps is nice and speedy.
1770 observer.addOnDrawListener(new ViewTreeObserver.OnDrawListener() {
1771 private boolean mStarted = false;
1772 public void onDraw() {
1773 if (mStarted) return;
1774 mStarted = true;
1775 // We delay the layer building a bit in order to give
1776 // other message processing a time to run. In particular
1777 // this avoids a delay in hiding the IME if it was
1778 // currently shown, because doing that may involve
1779 // some communication back with the app.
1780 mWorkspace.postDelayed(mBuildLayersRunnable, 500);
1781 final ViewTreeObserver.OnDrawListener listener = this;
1782 mWorkspace.post(new Runnable() {
1783 public void run() {
1784 if (mWorkspace != null &&
1785 mWorkspace.getViewTreeObserver() != null) {
1786 mWorkspace.getViewTreeObserver().
1787 removeOnDrawListener(listener);
1788 }
1789 }
1790 });
1791 return;
1792 }
1793 });
1794 }
1795 clearTypedText();
1796 }
1797 }
1798
1799 @Thunk void sendAdvanceMessage(long delay) {
1800 mHandler.removeMessages(ADVANCE_MSG);
1801 Message msg = mHandler.obtainMessage(ADVANCE_MSG);
1802 mHandler.sendMessageDelayed(msg, delay);
1803 mAutoAdvanceSentTime = System.currentTimeMillis();
1804 }
1805
1806 @Thunk void updateAutoAdvanceState() {
1807 boolean autoAdvanceRunning = mVisible && mUserPresent && !mWidgetsToAdvance.isEmpty();
1808 if (autoAdvanceRunning != mAutoAdvanceRunning) {
1809 mAutoAdvanceRunning = autoAdvanceRunning;
1810 if (autoAdvanceRunning) {
1811 long delay = mAutoAdvanceTimeLeft == -1 ? mAdvanceInterval : mAutoAdvanceTimeLeft;
1812 sendAdvanceMessage(delay);
1813 } else {
1814 if (!mWidgetsToAdvance.isEmpty()) {
1815 mAutoAdvanceTimeLeft = Math.max(0, mAdvanceInterval -
1816 (System.currentTimeMillis() - mAutoAdvanceSentTime));
1817 }
1818 mHandler.removeMessages(ADVANCE_MSG);
1819 mHandler.removeMessages(0); // Remove messages sent using postDelayed()
1820 }
1821 }
1822 }
1823
1824 private final Handler mHandler = new Handler() {
1825 @Override
1826 public void handleMessage(Message msg) {
1827 if (msg.what == ADVANCE_MSG) {
1828 int i = 0;
1829 for (View key: mWidgetsToAdvance.keySet()) {
1830 final View v = key.findViewById(mWidgetsToAdvance.get(key).autoAdvanceViewId);
1831 final int delay = mAdvanceStagger * i;
1832 if (v instanceof Advanceable) {
1833 postDelayed(new Runnable() {
1834 public void run() {
1835 ((Advanceable) v).advance();
1836 }
1837 }, delay);
1838 }
1839 i++;
1840 }
1841 sendAdvanceMessage(mAdvanceInterval);
1842 }
1843 }
1844 };
1845
1846 void addWidgetToAutoAdvanceIfNeeded(View hostView, AppWidgetProviderInfo appWidgetInfo) {
1847 if (appWidgetInfo == null || appWidgetInfo.autoAdvanceViewId == -1) return;
1848 View v = hostView.findViewById(appWidgetInfo.autoAdvanceViewId);
1849 if (v instanceof Advanceable) {
1850 mWidgetsToAdvance.put(hostView, appWidgetInfo);
1851 ((Advanceable) v).fyiWillBeAdvancedByHostKThx();
1852 updateAutoAdvanceState();
1853 }
1854 }
1855
1856 void removeWidgetToAutoAdvance(View hostView) {
1857 if (mWidgetsToAdvance.containsKey(hostView)) {
1858 mWidgetsToAdvance.remove(hostView);
1859 updateAutoAdvanceState();
1860 }
1861 }
1862
1863 public void removeAppWidget(LauncherAppWidgetInfo launcherInfo) {
1864 removeWidgetToAutoAdvance(launcherInfo.hostView);
1865 launcherInfo.hostView = null;
1866 }
1867
1868 public void showOutOfSpaceMessage(boolean isHotseatLayout) {
1869 int strId = (isHotseatLayout ? R.string.hotseat_out_of_space : R.string.out_of_space);
1870 Toast.makeText(this, getString(strId), Toast.LENGTH_SHORT).show();
1871 }
1872
1873 public DragLayer getDragLayer() {
1874 return mDragLayer;
1875 }
1876
1877 public AllAppsContainerView getAppsView() {
1878 return mAppsView;
1879 }
1880
1881 public WidgetsContainerView getWidgetsView() {
1882 return mWidgetsView;
1883 }
1884
1885 public Workspace getWorkspace() {
1886 return mWorkspace;
1887 }
1888
1889 public Hotseat getHotseat() {
1890 return mHotseat;
1891 }
1892
1893 public ViewGroup getOverviewPanel() {
1894 return mOverviewPanel;
1895 }
1896
1897 public SearchDropTargetBar getSearchBar() {
1898 return mSearchDropTargetBar;
1899 }
1900
1901 public LauncherAppWidgetHost getAppWidgetHost() {
1902 return mAppWidgetHost;
1903 }
1904
1905 public LauncherModel getModel() {
1906 return mModel;
1907 }
1908
1909 protected SharedPreferences getSharedPrefs() {
1910 return mSharedPrefs;
1911 }
1912
1913 public DeviceProfile getDeviceProfile() {
1914 return mDeviceProfile;
1915 }
1916
1917 public void closeSystemDialogs() {
1918 getWindow().closeAllPanels();
1919
1920 // Whatever we were doing is hereby canceled.
1921 setWaitingForResult(false);
1922 }
1923
1924 @Override
1925 protected void onNewIntent(Intent intent) {
1926 long startTime = 0;
1927 if (DEBUG_RESUME_TIME) {
1928 startTime = System.currentTimeMillis();
1929 }
1930 super.onNewIntent(intent);
1931
1932 // Close the menu
1933 if (Intent.ACTION_MAIN.equals(intent.getAction())) {
1934 // also will cancel mWaitingForResult.
1935 closeSystemDialogs();
1936
1937 final boolean alreadyOnHome = mHasFocus && ((intent.getFlags() &
1938 Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT)
1939 != Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT);
1940
1941 if (mWorkspace == null) {
1942 // Can be cases where mWorkspace is null, this prevents a NPE
1943 return;
1944 }
1945 Folder openFolder = mWorkspace.getOpenFolder();
1946 // In all these cases, only animate if we're already on home
1947 mWorkspace.exitWidgetResizeMode();
1948
1949 boolean moveToDefaultScreen = mLauncherCallbacks != null ?
1950 mLauncherCallbacks.shouldMoveToDefaultScreenOnHomeIntent() : true;
1951 if (alreadyOnHome && mState == State.WORKSPACE && !mWorkspace.isTouchActive() &&
1952 openFolder == null && moveToDefaultScreen) {
1953 mWorkspace.moveToDefaultScreen(true);
1954 }
1955
1956 closeFolder();
1957 exitSpringLoadedDragMode();
1958
1959 // If we are already on home, then just animate back to the workspace,
1960 // otherwise, just wait until onResume to set the state back to Workspace
1961 if (alreadyOnHome) {
1962 showWorkspace(true);
1963 } else {
1964 mOnResumeState = State.WORKSPACE;
1965 }
1966
1967 final View v = getWindow().peekDecorView();
1968 if (v != null && v.getWindowToken() != null) {
1969 InputMethodManager imm = (InputMethodManager)getSystemService(
1970 INPUT_METHOD_SERVICE);
1971 imm.hideSoftInputFromWindow(v.getWindowToken(), 0);
1972 }
1973
1974 // Reset the apps view
1975 if (!alreadyOnHome && mAppsView != null) {
1976 mAppsView.scrollToTop();
1977 }
1978
1979 // Reset the widgets view
1980 if (!alreadyOnHome && mWidgetsView != null) {
1981 mWidgetsView.scrollToTop();
1982 }
1983
1984 if (mLauncherCallbacks != null) {
1985 mLauncherCallbacks.onHomeIntent();
1986 }
1987 }
1988
1989 if (DEBUG_RESUME_TIME) {
1990 Log.d(TAG, "Time spent in onNewIntent: " + (System.currentTimeMillis() - startTime));
1991 }
1992
1993 if (mLauncherCallbacks != null) {
1994 mLauncherCallbacks.onNewIntent(intent);
1995 }
1996 }
1997
1998 @Override
1999 public void onRestoreInstanceState(Bundle state) {
2000 super.onRestoreInstanceState(state);
2001 for (int page: mSynchronouslyBoundPages) {
2002 mWorkspace.restoreInstanceStateForChild(page);
2003 }
2004 }
2005
2006 @Override
2007 protected void onSaveInstanceState(Bundle outState) {
2008 if (mWorkspace.getChildCount() > 0) {
2009 outState.putInt(RUNTIME_STATE_CURRENT_SCREEN,
2010 mWorkspace.getCurrentPageOffsetFromCustomContent());
2011 }
2012 super.onSaveInstanceState(outState);
2013
2014 outState.putInt(RUNTIME_STATE, mState.ordinal());
2015 // We close any open folder since it will not be re-opened, and we need to make sure
2016 // this state is reflected.
2017 closeFolder();
2018
2019 if (mPendingAddInfo.container != ItemInfo.NO_ID && mPendingAddInfo.screenId > -1 &&
2020 mWaitingForResult) {
2021 outState.putLong(RUNTIME_STATE_PENDING_ADD_CONTAINER, mPendingAddInfo.container);
2022 outState.putLong(RUNTIME_STATE_PENDING_ADD_SCREEN, mPendingAddInfo.screenId);
2023 outState.putInt(RUNTIME_STATE_PENDING_ADD_CELL_X, mPendingAddInfo.cellX);
2024 outState.putInt(RUNTIME_STATE_PENDING_ADD_CELL_Y, mPendingAddInfo.cellY);
2025 outState.putInt(RUNTIME_STATE_PENDING_ADD_SPAN_X, mPendingAddInfo.spanX);
2026 outState.putInt(RUNTIME_STATE_PENDING_ADD_SPAN_Y, mPendingAddInfo.spanY);
2027 outState.putParcelable(RUNTIME_STATE_PENDING_ADD_WIDGET_INFO, mPendingAddWidgetInfo);
2028 outState.putInt(RUNTIME_STATE_PENDING_ADD_WIDGET_ID, mPendingAddWidgetId);
2029 }
2030
2031 // Save the current widgets tray?
2032 // TODO(hyunyoungs)
2033 outState.putSerializable(RUNTIME_STATE_VIEW_IDS, mItemIdToViewId);
2034
2035 if (mLauncherCallbacks != null) {
2036 mLauncherCallbacks.onSaveInstanceState(outState);
2037 }
2038 }
2039
2040 @Override
2041 public void onDestroy() {
2042 super.onDestroy();
2043
2044 // Remove all pending runnables
2045 mHandler.removeMessages(ADVANCE_MSG);
2046 mHandler.removeMessages(0);
2047 mWorkspace.removeCallbacks(mBuildLayersRunnable);
2048
2049 // Stop callbacks from LauncherModel
2050 LauncherAppState app = (LauncherAppState.getInstance());
2051
2052 // It's possible to receive onDestroy after a new Launcher activity has
2053 // been created. In this case, don't interfere with the new Launcher.
2054 if (mModel.isCurrentCallbacks(this)) {
2055 mModel.stopLoader();
2056 app.setLauncher(null);
2057 }
2058
2059 try {
2060 mAppWidgetHost.stopListening();
2061 } catch (NullPointerException ex) {
2062 Log.w(TAG, "problem while stopping AppWidgetHost during Launcher destruction", ex);
2063 }
2064 mAppWidgetHost = null;
2065
2066 mWidgetsToAdvance.clear();
2067
2068 TextKeyListener.getInstance().release();
2069
2070 getContentResolver().unregisterContentObserver(mWidgetObserver);
2071 unregisterReceiver(mCloseSystemDialogsReceiver);
2072
2073 mDragLayer.clearAllResizeFrames();
2074 ((ViewGroup) mWorkspace.getParent()).removeAllViews();
2075 mWorkspace.removeAllWorkspaceScreens();
2076 mWorkspace = null;
2077 mDragController = null;
2078
2079 LauncherAnimUtils.onDestroyActivity();
2080
2081 if (mLauncherCallbacks != null) {
2082 mLauncherCallbacks.onDestroy();
2083 }
2084 }
2085
2086 public DragController getDragController() {
2087 return mDragController;
2088 }
2089
2090 @Override
2091 public void startActivityForResult(Intent intent, int requestCode) {
2092 onStartForResult(requestCode);
2093 super.startActivityForResult(intent, requestCode);
2094 }
2095
2096 @Override
2097 public void startIntentSenderForResult (IntentSender intent, int requestCode,
2098 Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags, Bundle options) {
2099 onStartForResult(requestCode);
2100 try {
2101 super.startIntentSenderForResult(intent, requestCode,
2102 fillInIntent, flagsMask, flagsValues, extraFlags, options);
2103 } catch (IntentSender.SendIntentException e) {
2104 throw new ActivityNotFoundException();
2105 }
2106 }
2107
2108 private void onStartForResult(int requestCode) {
2109 if (requestCode >= 0) {
2110 setWaitingForResult(true);
2111 }
2112 }
2113
2114 /**
2115 * Indicates that we want global search for this activity by setting the globalSearch
2116 * argument for {@link #startSearch} to true.
2117 */
2118 @Override
2119 public void startSearch(String initialQuery, boolean selectInitialQuery,
2120 Bundle appSearchData, boolean globalSearch) {
2121
2122 if (initialQuery == null) {
2123 // Use any text typed in the launcher as the initial query
2124 initialQuery = getTypedText();
2125 }
2126 if (appSearchData == null) {
2127 appSearchData = new Bundle();
2128 appSearchData.putString("source", "launcher-search");
2129 }
2130 Rect sourceBounds = new Rect();
2131 if (mSearchDropTargetBar != null) {
2132 sourceBounds = mSearchDropTargetBar.getSearchBarBounds();
2133 }
2134
2135 boolean clearTextImmediately = startSearch(initialQuery, selectInitialQuery,
2136 appSearchData, sourceBounds);
2137 if (clearTextImmediately) {
2138 clearTypedText();
2139 }
2140
2141 // We need to show the workspace after starting the search
2142 showWorkspace(true);
2143 }
2144
2145 /**
2146 * Start a text search.
2147 *
2148 * @return {@code true} if the search will start immediately, so any further keypresses
2149 * will be handled directly by the search UI. {@code false} if {@link Launcher} should continue
2150 * to buffer keypresses.
2151 */
2152 public boolean startSearch(String initialQuery,
2153 boolean selectInitialQuery, Bundle appSearchData, Rect sourceBounds) {
2154 if (mLauncherCallbacks != null && mLauncherCallbacks.providesSearch()) {
2155 return mLauncherCallbacks.startSearch(initialQuery, selectInitialQuery, appSearchData,
2156 sourceBounds);
2157 }
2158
2159 startGlobalSearch(initialQuery, selectInitialQuery,
2160 appSearchData, sourceBounds);
2161 return false;
2162 }
2163
2164 /**
2165 * Starts the global search activity. This code is a copied from SearchManager
2166 */
2167 private void startGlobalSearch(String initialQuery,
2168 boolean selectInitialQuery, Bundle appSearchData, Rect sourceBounds) {
2169 final SearchManager searchManager =
2170 (SearchManager) getSystemService(Context.SEARCH_SERVICE);
2171 ComponentName globalSearchActivity = searchManager.getGlobalSearchActivity();
2172 if (globalSearchActivity == null) {
2173 Log.w(TAG, "No global search activity found.");
2174 return;
2175 }
2176 Intent intent = new Intent(SearchManager.INTENT_ACTION_GLOBAL_SEARCH);
2177 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
2178 intent.setComponent(globalSearchActivity);
2179 // Make sure that we have a Bundle to put source in
2180 if (appSearchData == null) {
2181 appSearchData = new Bundle();
2182 } else {
2183 appSearchData = new Bundle(appSearchData);
2184 }
2185 // Set source to package name of app that starts global search if not set already.
2186 if (!appSearchData.containsKey("source")) {
2187 appSearchData.putString("source", getPackageName());
2188 }
2189 intent.putExtra(SearchManager.APP_DATA, appSearchData);
2190 if (!TextUtils.isEmpty(initialQuery)) {
2191 intent.putExtra(SearchManager.QUERY, initialQuery);
2192 }
2193 if (selectInitialQuery) {
2194 intent.putExtra(SearchManager.EXTRA_SELECT_QUERY, selectInitialQuery);
2195 }
2196 intent.setSourceBounds(sourceBounds);
2197 try {
2198 startActivity(intent);
2199 } catch (ActivityNotFoundException ex) {
2200 Log.e(TAG, "Global search activity not found: " + globalSearchActivity);
2201 }
2202 }
2203
2204 public boolean isOnCustomContent() {
2205 return mWorkspace.isOnOrMovingToCustomContent();
2206 }
2207
2208 @Override
2209 public boolean onPrepareOptionsMenu(Menu menu) {
2210 super.onPrepareOptionsMenu(menu);
2211 if (!isOnCustomContent()) {
2212 // Close any open folders
2213 closeFolder();
2214 // Stop resizing any widgets
2215 mWorkspace.exitWidgetResizeMode();
2216 if (!mWorkspace.isInOverviewMode()) {
2217 // Show the overview mode
2218 showOverviewMode(true);
2219 } else {
2220 showWorkspace(true);
2221 }
2222 }
2223 if (mLauncherCallbacks != null) {
2224 return mLauncherCallbacks.onPrepareOptionsMenu(menu);
2225 }
2226
2227 return false;
2228 }
2229
2230 @Override
2231 public boolean onSearchRequested() {
2232 startSearch(null, false, null, true);
2233 // Use a custom animation for launching search
2234 return true;
2235 }
2236
2237 public boolean isWorkspaceLocked() {
2238 return mWorkspaceLoading || mWaitingForResult;
2239 }
2240
2241 public boolean isWorkspaceLoading() {
2242 return mWorkspaceLoading;
2243 }
2244
2245 private void setWorkspaceLoading(boolean value) {
2246 boolean isLocked = isWorkspaceLocked();
2247 mWorkspaceLoading = value;
2248 if (isLocked != isWorkspaceLocked()) {
2249 onWorkspaceLockedChanged();
2250 }
2251 }
2252
2253 private void setWaitingForResult(boolean value) {
2254 boolean isLocked = isWorkspaceLocked();
2255 mWaitingForResult = value;
2256 if (isLocked != isWorkspaceLocked()) {
2257 onWorkspaceLockedChanged();
2258 }
2259 }
2260
2261 protected void onWorkspaceLockedChanged() {
2262 if (mLauncherCallbacks != null) {
2263 mLauncherCallbacks.onWorkspaceLockedChanged();
2264 }
2265 }
2266
2267 private void resetAddInfo() {
2268 mPendingAddInfo.container = ItemInfo.NO_ID;
2269 mPendingAddInfo.screenId = -1;
2270 mPendingAddInfo.cellX = mPendingAddInfo.cellY = -1;
2271 mPendingAddInfo.spanX = mPendingAddInfo.spanY = -1;
2272 mPendingAddInfo.minSpanX = mPendingAddInfo.minSpanY = -1;
2273 mPendingAddInfo.dropPos = null;
2274 }
2275
2276 void addAppWidgetImpl(final int appWidgetId, final ItemInfo info, final
2277 AppWidgetHostView boundWidget, final LauncherAppWidgetProviderInfo appWidgetInfo) {
2278 addAppWidgetImpl(appWidgetId, info, boundWidget, appWidgetInfo, 0);
2279 }
2280
2281 void addAppWidgetImpl(final int appWidgetId, final ItemInfo info,
2282 final AppWidgetHostView boundWidget, final LauncherAppWidgetProviderInfo appWidgetInfo,
2283 int delay) {
2284 if (appWidgetInfo.configure != null) {
2285 mPendingAddWidgetInfo = appWidgetInfo;
2286 mPendingAddWidgetId = appWidgetId;
2287
2288 // Launch over to configure widget, if needed
2289 mAppWidgetManager.startConfigActivity(appWidgetInfo, appWidgetId, this,
2290 mAppWidgetHost, REQUEST_CREATE_APPWIDGET);
2291
2292 } else {
2293 // Otherwise just add it
2294 Runnable onComplete = new Runnable() {
2295 @Override
2296 public void run() {
2297 // Exit spring loaded mode if necessary after adding the widget
2298 exitSpringLoadedDragModeDelayed(true, EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT,
2299 null);
2300 }
2301 };
2302 completeAddAppWidget(appWidgetId, info.container, info.screenId, boundWidget,
2303 appWidgetInfo);
2304 mWorkspace.removeExtraEmptyScreenDelayed(true, onComplete, delay, false);
2305 }
2306 }
2307
2308 protected void moveToCustomContentScreen(boolean animate) {
2309 // Close any folders that may be open.
2310 closeFolder();
2311 mWorkspace.moveToCustomContentScreen(animate);
2312 }
2313
2314 public void addPendingItem(PendingAddItemInfo info, long container, long screenId,
2315 int[] cell, int spanX, int spanY) {
2316 switch (info.itemType) {
2317 case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET:
2318 case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
2319 int span[] = new int[2];
2320 span[0] = spanX;
2321 span[1] = spanY;
2322 addAppWidgetFromDrop((PendingAddWidgetInfo) info,
2323 container, screenId, cell, span);
2324 break;
2325 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
2326 processShortcutFromDrop(info.componentName, container, screenId, cell);
2327 break;
2328 default:
2329 throw new IllegalStateException("Unknown item type: " + info.itemType);
2330 }
2331 }
2332
2333 /**
2334 * Process a shortcut drop.
2335 *
2336 * @param componentName The name of the component
2337 * @param screenId The ID of the screen where it should be added
2338 * @param cell The cell it should be added to, optional
2339 */
2340 private void processShortcutFromDrop(ComponentName componentName, long container, long screenId,
2341 int[] cell) {
2342 resetAddInfo();
2343 mPendingAddInfo.container = container;
2344 mPendingAddInfo.screenId = screenId;
2345 mPendingAddInfo.dropPos = null;
2346
2347 if (cell != null) {
2348 mPendingAddInfo.cellX = cell[0];
2349 mPendingAddInfo.cellY = cell[1];
2350 }
2351
2352 Intent createShortcutIntent = new Intent(Intent.ACTION_CREATE_SHORTCUT);
2353 createShortcutIntent.setComponent(componentName);
2354 processShortcut(createShortcutIntent);
2355 }
2356
2357 /**
2358 * Process a widget drop.
2359 *
2360 * @param info The PendingAppWidgetInfo of the widget being added.
2361 * @param screenId The ID of the screen where it should be added
2362 * @param cell The cell it should be added to, optional
2363 */
2364 private void addAppWidgetFromDrop(PendingAddWidgetInfo info, long container, long screenId,
2365 int[] cell, int[] span) {
2366 resetAddInfo();
2367 mPendingAddInfo.container = info.container = container;
2368 mPendingAddInfo.screenId = info.screenId = screenId;
2369 mPendingAddInfo.dropPos = null;
2370 mPendingAddInfo.minSpanX = info.minSpanX;
2371 mPendingAddInfo.minSpanY = info.minSpanY;
2372
2373 if (cell != null) {
2374 mPendingAddInfo.cellX = cell[0];
2375 mPendingAddInfo.cellY = cell[1];
2376 }
2377 if (span != null) {
2378 mPendingAddInfo.spanX = span[0];
2379 mPendingAddInfo.spanY = span[1];
2380 }
2381
2382 AppWidgetHostView hostView = info.boundWidget;
2383 int appWidgetId;
2384 if (hostView != null) {
2385 appWidgetId = hostView.getAppWidgetId();
2386 addAppWidgetImpl(appWidgetId, info, hostView, info.info);
2387 } else {
2388 // In this case, we either need to start an activity to get permission to bind
2389 // the widget, or we need to start an activity to configure the widget, or both.
2390 appWidgetId = getAppWidgetHost().allocateAppWidgetId();
2391 Bundle options = info.bindOptions;
2392
2393 boolean success = mAppWidgetManager.bindAppWidgetIdIfAllowed(
2394 appWidgetId, info.info, options);
2395 if (success) {
2396 addAppWidgetImpl(appWidgetId, info, null, info.info);
2397 } else {
2398 mPendingAddWidgetInfo = info.info;
2399 Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_BIND);
2400 intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
2401 intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER, info.componentName);
2402 mAppWidgetManager.getUser(mPendingAddWidgetInfo)
2403 .addToIntent(intent, AppWidgetManager.EXTRA_APPWIDGET_PROVIDER_PROFILE);
2404 // TODO: we need to make sure that this accounts for the options bundle.
2405 // intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_OPTIONS, options);
2406 startActivityForResult(intent, REQUEST_BIND_APPWIDGET);
2407 }
2408 }
2409 }
2410
2411 void processShortcut(Intent intent) {
2412 Utilities.startActivityForResultSafely(this, intent, REQUEST_CREATE_SHORTCUT);
2413 }
2414
2415 void processWallpaper(Intent intent) {
2416 startActivityForResult(intent, REQUEST_PICK_WALLPAPER);
2417 }
2418
2419 FolderIcon addFolder(CellLayout layout, long container, final long screenId, int cellX,
2420 int cellY) {
2421 final FolderInfo folderInfo = new FolderInfo();
2422 folderInfo.title = getText(R.string.folder_name);
2423
2424 // Update the model
2425 LauncherModel.addItemToDatabase(Launcher.this, folderInfo, container, screenId,
2426 cellX, cellY);
2427 sFolders.put(folderInfo.id, folderInfo);
2428
2429 // Create the view
2430 FolderIcon newFolder =
2431 FolderIcon.fromXml(R.layout.folder_icon, this, layout, folderInfo, mIconCache);
2432 mWorkspace.addInScreen(newFolder, container, screenId, cellX, cellY, 1, 1,
2433 isWorkspaceLocked());
2434 // Force measure the new folder icon
2435 CellLayout parent = mWorkspace.getParentCellLayoutForView(newFolder);
2436 parent.getShortcutsAndWidgets().measureChild(newFolder);
2437 return newFolder;
2438 }
2439
2440 void removeFolder(FolderInfo folder) {
2441 sFolders.remove(folder.id);
2442 }
2443
2444 /**
2445 * Registers various content observers. The current implementation registers
2446 * only a favorites observer to keep track of the favorites applications.
2447 */
2448 private void registerContentObservers() {
2449 ContentResolver resolver = getContentResolver();
2450 resolver.registerContentObserver(LauncherProvider.CONTENT_APPWIDGET_RESET_URI,
2451 true, mWidgetObserver);
2452 }
2453
2454 @Override
2455 public boolean dispatchKeyEvent(KeyEvent event) {
2456 if (event.getAction() == KeyEvent.ACTION_DOWN) {
2457 switch (event.getKeyCode()) {
2458 case KeyEvent.KEYCODE_HOME:
2459 return true;
2460 case KeyEvent.KEYCODE_VOLUME_DOWN:
2461 if (Utilities.isPropertyEnabled(DUMP_STATE_PROPERTY)) {
2462 dumpState();
2463 return true;
2464 }
2465 break;
2466 }
2467 } else if (event.getAction() == KeyEvent.ACTION_UP) {
2468 switch (event.getKeyCode()) {
2469 case KeyEvent.KEYCODE_HOME:
2470 return true;
2471 }
2472 }
2473
2474 return super.dispatchKeyEvent(event);
2475 }
2476
2477 @Override
2478 public void onBackPressed() {
2479 if (mLauncherCallbacks != null && mLauncherCallbacks.handleBackPressed()) {
2480 return;
2481 }
2482
2483 LauncherAccessibilityDelegate delegate =
2484 LauncherAppState.getInstance().getAccessibilityDelegate();
2485 if (delegate != null && delegate.onBackPressed()) {
2486 return;
2487 }
2488
2489 if (isAppsViewVisible()) {
2490 showWorkspace(true);
2491 } else if (isWidgetsViewVisible()) {
2492 showOverviewMode(true);
2493 } else if (mWorkspace.isInOverviewMode()) {
2494 showWorkspace(true);
2495 } else if (mWorkspace.getOpenFolder() != null) {
2496 Folder openFolder = mWorkspace.getOpenFolder();
2497 if (openFolder.isEditingName()) {
2498 openFolder.dismissEditingName();
2499 } else {
2500 closeFolder();
2501 }
2502 } else {
2503 mWorkspace.exitWidgetResizeMode();
2504
2505 // Back button is a no-op here, but give at least some feedback for the button press
2506 mWorkspace.showOutlinesTemporarily();
2507 }
2508 }
2509
2510 /**
2511 * Re-listen when widgets are reset.
2512 */
2513 @Thunk void onAppWidgetReset() {
2514 if (mAppWidgetHost != null) {
2515 mAppWidgetHost.startListening();
2516 }
2517 }
2518
2519 /**
2520 * Launches the intent referred by the clicked shortcut.
2521 *
2522 * @param v The view representing the clicked shortcut.
2523 */
2524 public void onClick(View v) {
2525 // Make sure that rogue clicks don't get through while allapps is launching, or after the
2526 // view has detached (it's possible for this to happen if the view is removed mid touch).
2527 if (v.getWindowToken() == null) {
2528 return;
2529 }
2530
2531 if (!mWorkspace.isFinishedSwitchingState()) {
2532 return;
2533 }
2534
2535 if (v instanceof Workspace) {
2536 if (mWorkspace.isInOverviewMode()) {
2537 showWorkspace(true);
2538 }
2539 return;
2540 }
2541
2542 if (v instanceof CellLayout) {
2543 if (mWorkspace.isInOverviewMode()) {
2544 showWorkspace(mWorkspace.indexOfChild(v), true);
2545 }
2546 }
2547
2548 Object tag = v.getTag();
2549 if (tag instanceof ShortcutInfo) {
2550 onClickAppShortcut(v);
2551 } else if (tag instanceof FolderInfo) {
2552 if (v instanceof FolderIcon) {
2553 onClickFolderIcon(v);
2554 }
2555 } else if (v == mAllAppsButton) {
2556 onClickAllAppsButton(v);
2557 } else if (tag instanceof AppInfo) {
2558 startAppShortcutOrInfoActivity(v);
2559 } else if (tag instanceof LauncherAppWidgetInfo) {
2560 if (v instanceof PendingAppWidgetHostView) {
2561 onClickPendingWidget((PendingAppWidgetHostView) v);
2562 }
2563 }
2564 }
2565
2566 public void onClickPagedViewIcon(View v) {
2567 startAppShortcutOrInfoActivity(v);
2568 if (mLauncherCallbacks != null) {
2569 mLauncherCallbacks.onClickPagedViewIcon(v);
2570 }
2571 }
2572
2573 @SuppressLint("ClickableViewAccessibility")
2574 public boolean onTouch(View v, MotionEvent event) {
2575 return false;
2576 }
2577
2578 /**
2579 * Event handler for the app widget view which has not fully restored.
2580 */
2581 public void onClickPendingWidget(final PendingAppWidgetHostView v) {
2582 if (mIsSafeModeEnabled) {
2583 Toast.makeText(this, R.string.safemode_widget_error, Toast.LENGTH_SHORT).show();
2584 return;
2585 }
2586
2587 final LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) v.getTag();
2588 if (v.isReadyForClickSetup()) {
2589 int widgetId = info.appWidgetId;
2590 AppWidgetProviderInfo appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(widgetId);
2591 if (appWidgetInfo != null) {
2592 mPendingAddWidgetInfo = LauncherAppWidgetProviderInfo.fromProviderInfo(
2593 this, appWidgetInfo);
2594 mPendingAddInfo.copyFrom(info);
2595 mPendingAddWidgetId = widgetId;
2596
2597 AppWidgetManagerCompat.getInstance(this).startConfigActivity(appWidgetInfo,
2598 info.appWidgetId, this, mAppWidgetHost, REQUEST_RECONFIGURE_APPWIDGET);
2599 }
2600 } else if (info.installProgress < 0) {
2601 // The install has not been queued
2602 final String packageName = info.providerName.getPackageName();
2603 showBrokenAppInstallDialog(packageName,
2604 new DialogInterface.OnClickListener() {
2605 public void onClick(DialogInterface dialog, int id) {
2606 startActivitySafely(v, LauncherModel.getMarketIntent(packageName), info);
2607 }
2608 });
2609 } else {
2610 // Download has started.
2611 final String packageName = info.providerName.getPackageName();
2612 startActivitySafely(v, LauncherModel.getMarketIntent(packageName), info);
2613 }
2614 }
2615
2616 /**
2617 * Event handler for the "grid" button that appears on the home screen, which
2618 * enters all apps mode.
2619 *
2620 * @param v The view that was clicked.
2621 */
2622 protected void onClickAllAppsButton(View v) {
2623 if (LOGD) Log.d(TAG, "onClickAllAppsButton");
2624 if (isAppsViewVisible()) {
2625 showWorkspace(true);
2626 } else {
2627 // Try and refresh the set of predicted apps before we enter launcher
2628 showAppsView(true /* animated */, false /* resetListToTop */,
2629 true /* updatePredictedApps */);
2630 }
2631 }
2632
2633 private void showBrokenAppInstallDialog(final String packageName,
2634 DialogInterface.OnClickListener onSearchClickListener) {
2635 new AlertDialog.Builder(this)
2636 .setTitle(R.string.abandoned_promises_title)
2637 .setMessage(R.string.abandoned_promise_explanation)
2638 .setPositiveButton(R.string.abandoned_search, onSearchClickListener)
2639 .setNeutralButton(R.string.abandoned_clean_this,
2640 new DialogInterface.OnClickListener() {
2641 public void onClick(DialogInterface dialog, int id) {
2642 final UserHandleCompat user = UserHandleCompat.myUserHandle();
2643 mWorkspace.removeAbandonedPromise(packageName, user);
2644 }
2645 })
2646 .create().show();
2647 return;
2648 }
2649
2650 /**
2651 * Event handler for an app shortcut click.
2652 *
2653 * @param v The view that was clicked. Must be a tagged with a {@link ShortcutInfo}.
2654 */
2655 protected void onClickAppShortcut(final View v) {
2656 if (LOGD) Log.d(TAG, "onClickAppShortcut");
2657 Object tag = v.getTag();
2658 if (!(tag instanceof ShortcutInfo)) {
2659 throw new IllegalArgumentException("Input must be a Shortcut");
2660 }
2661
2662 // Open shortcut
2663 final ShortcutInfo shortcut = (ShortcutInfo) tag;
2664
2665 if (shortcut.isDisabled != 0) {
2666 int error = R.string.activity_not_available;
2667 if ((shortcut.isDisabled & ShortcutInfo.FLAG_DISABLED_SAFEMODE) != 0) {
2668 error = R.string.safemode_shortcut_error;
2669 }
2670 Toast.makeText(this, error, Toast.LENGTH_SHORT).show();
2671 return;
2672 }
2673
2674 final Intent intent = shortcut.intent;
2675
2676 // Check for special shortcuts
2677 if (intent.getComponent() != null) {
2678 final String shortcutClass = intent.getComponent().getClassName();
2679
2680 if (shortcutClass.equals(MemoryDumpActivity.class.getName())) {
2681 MemoryDumpActivity.startDump(this);
2682 return;
2683 } else if (shortcutClass.equals(ToggleWeightWatcher.class.getName())) {
2684 toggleShowWeightWatcher();
2685 return;
2686 }
2687 }
2688
2689 // Check for abandoned promise
2690 if ((v instanceof BubbleTextView)
2691 && shortcut.isPromise()
2692 && !shortcut.hasStatusFlag(ShortcutInfo.FLAG_INSTALL_SESSION_ACTIVE)) {
2693 showBrokenAppInstallDialog(
2694 shortcut.getTargetComponent().getPackageName(),
2695 new DialogInterface.OnClickListener() {
2696 public void onClick(DialogInterface dialog, int id) {
2697 startAppShortcutOrInfoActivity(v);
2698 }
2699 });
2700 return;
2701 }
2702
2703 // Start activities
2704 startAppShortcutOrInfoActivity(v);
2705
2706 if (mLauncherCallbacks != null) {
2707 mLauncherCallbacks.onClickAppShortcut(v);
2708 }
2709 }
2710
2711 @Thunk void startAppShortcutOrInfoActivity(View v) {
2712 Object tag = v.getTag();
2713 final ShortcutInfo shortcut;
2714 final Intent intent;
2715 if (tag instanceof ShortcutInfo) {
2716 shortcut = (ShortcutInfo) tag;
2717 intent = shortcut.intent;
2718 int[] pos = new int[2];
2719 v.getLocationOnScreen(pos);
2720 intent.setSourceBounds(new Rect(pos[0], pos[1],
2721 pos[0] + v.getWidth(), pos[1] + v.getHeight()));
2722
2723 } else if (tag instanceof AppInfo) {
2724 shortcut = null;
2725 intent = ((AppInfo) tag).intent;
2726 } else {
2727 throw new IllegalArgumentException("Input must be a Shortcut or AppInfo");
2728 }
2729
2730 boolean success = startActivitySafely(v, intent, tag);
2731 mStats.recordLaunch(intent, shortcut);
2732
2733 if (success && v instanceof BubbleTextView) {
2734 mWaitingForResume = (BubbleTextView) v;
2735 mWaitingForResume.setStayPressed(true);
2736 }
2737 }
2738
2739 /**
2740 * Event handler for a folder icon click.
2741 *
2742 * @param v The view that was clicked. Must be an instance of {@link FolderIcon}.
2743 */
2744 protected void onClickFolderIcon(View v) {
2745 if (LOGD) Log.d(TAG, "onClickFolder");
2746 if (!(v instanceof FolderIcon)){
2747 throw new IllegalArgumentException("Input must be a FolderIcon");
2748 }
2749
2750 FolderIcon folderIcon = (FolderIcon) v;
2751 final FolderInfo info = folderIcon.getFolderInfo();
2752 Folder openFolder = mWorkspace.getFolderForTag(info);
2753
2754 // If the folder info reports that the associated folder is open, then verify that
2755 // it is actually opened. There have been a few instances where this gets out of sync.
2756 if (info.opened && openFolder == null) {
2757 Log.d(TAG, "Folder info marked as open, but associated folder is not open. Screen: "
2758 + info.screenId + " (" + info.cellX + ", " + info.cellY + ")");
2759 info.opened = false;
2760 }
2761
2762 if (!info.opened && !folderIcon.getFolder().isDestroyed()) {
2763 // Close any open folder
2764 closeFolder();
2765 // Open the requested folder
2766 openFolder(folderIcon);
2767 } else {
2768 // Find the open folder...
2769 int folderScreen;
2770 if (openFolder != null) {
2771 folderScreen = mWorkspace.getPageForView(openFolder);
2772 // .. and close it
2773 closeFolder(openFolder);
2774 if (folderScreen != mWorkspace.getCurrentPage()) {
2775 // Close any folder open on the current screen
2776 closeFolder();
2777 // Pull the folder onto this screen
2778 openFolder(folderIcon);
2779 }
2780 }
2781 }
2782
2783 if (mLauncherCallbacks != null) {
2784 mLauncherCallbacks.onClickFolderIcon(v);
2785 }
2786 }
2787
2788 /**
2789 * Event handler for the (Add) Widgets button that appears after a long press
2790 * on the home screen.
2791 */
2792 protected void onClickAddWidgetButton(View view) {
2793 if (LOGD) Log.d(TAG, "onClickAddWidgetButton");
2794 if (mIsSafeModeEnabled) {
2795 Toast.makeText(this, R.string.safemode_widget_error, Toast.LENGTH_SHORT).show();
2796 } else {
2797 showWidgetsView(true /* animated */, true /* resetPageToZero */);
2798 if (mLauncherCallbacks != null) {
2799 mLauncherCallbacks.onClickAddWidgetButton(view);
2800 }
2801 }
2802 }
2803
2804 /**
2805 * Event handler for the wallpaper picker button that appears after a long press
2806 * on the home screen.
2807 */
2808 protected void onClickWallpaperPicker(View v) {
2809 if (LOGD) Log.d(TAG, "onClickWallpaperPicker");
2810 startActivityForResult(new Intent(Intent.ACTION_SET_WALLPAPER).setPackage(getPackageName()),
2811 REQUEST_PICK_WALLPAPER);
2812
2813 if (mLauncherCallbacks != null) {
2814 mLauncherCallbacks.onClickWallpaperPicker(v);
2815 }
2816 }
2817
2818 /**
2819 * Event handler for a click on the settings button that appears after a long press
2820 * on the home screen.
2821 */
2822 protected void onClickSettingsButton(View v) {
2823 if (LOGD) Log.d(TAG, "onClickSettingsButton");
2824 if (mLauncherCallbacks != null) {
2825 mLauncherCallbacks.onClickSettingsButton(v);
2826 }
2827 }
2828
2829 public void onTouchDownAllAppsButton(View v) {
2830 // Provide the same haptic feedback that the system offers for virtual keys.
2831 v.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
2832 }
2833
2834 public void performHapticFeedbackOnTouchDown(View v) {
2835 // Provide the same haptic feedback that the system offers for virtual keys.
2836 v.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
2837 }
2838
2839 public View.OnTouchListener getHapticFeedbackTouchListener() {
2840 if (mHapticFeedbackTouchListener == null) {
2841 mHapticFeedbackTouchListener = new View.OnTouchListener() {
2842 @SuppressLint("ClickableViewAccessibility")
2843 @Override
2844 public boolean onTouch(View v, MotionEvent event) {
2845 if ((event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_DOWN) {
2846 v.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
2847 }
2848 return false;
2849 }
2850 };
2851 }
2852 return mHapticFeedbackTouchListener;
2853 }
2854
2855 public void onDragStarted(View view) {
2856 if (isOnCustomContent()) {
2857 // Custom content screen doesn't participate in drag and drop. If on custom
2858 // content screen, move to default.
2859 moveWorkspaceToDefaultScreen();
2860 }
2861
2862 if (mLauncherCallbacks != null) {
2863 mLauncherCallbacks.onDragStarted(view);
2864 }
2865 }
2866
2867 /**
2868 * Called when the user stops interacting with the launcher.
2869 * This implies that the user is now on the homescreen and is not doing housekeeping.
2870 */
2871 protected void onInteractionEnd() {
2872 if (mLauncherCallbacks != null) {
2873 mLauncherCallbacks.onInteractionEnd();
2874 }
2875 }
2876
2877 /**
2878 * Called when the user starts interacting with the launcher.
2879 * The possible interactions are:
2880 * - open all apps
2881 * - reorder an app shortcut, or a widget
2882 * - open the overview mode.
2883 * This is a good time to stop doing things that only make sense
2884 * when the user is on the homescreen and not doing housekeeping.
2885 */
2886 protected void onInteractionBegin() {
2887 if (mLauncherCallbacks != null) {
2888 mLauncherCallbacks.onInteractionBegin();
2889 }
2890 }
2891
2892 /** Updates the interaction state. */
2893 public void updateInteraction(Workspace.State fromState, Workspace.State toState) {
2894 // Only update the interacting state if we are transitioning to/from a view with an
2895 // overlay
2896 boolean fromStateWithOverlay;
2897 boolean toStateWithOverlay;
2898 if (Launcher.DISABLE_ALL_APPS_SEARCH_INTEGRATION) {
2899 fromStateWithOverlay = fromState != Workspace.State.NORMAL;
2900 toStateWithOverlay = toState != Workspace.State.NORMAL;
2901 } else {
2902 fromStateWithOverlay = fromState != Workspace.State.NORMAL &&
2903 fromState != Workspace.State.NORMAL_HIDDEN;
2904 toStateWithOverlay = toState != Workspace.State.NORMAL &&
2905 toState != Workspace.State.NORMAL_HIDDEN;
2906 }
2907 if (toStateWithOverlay) {
2908 onInteractionBegin();
2909 } else if (fromStateWithOverlay) {
2910 onInteractionEnd();
2911 }
2912 }
2913
2914 void startApplicationDetailsActivity(ComponentName componentName, UserHandleCompat user) {
2915 try {
2916 LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(this);
2917 launcherApps.showAppDetailsForProfile(componentName, user);
2918 } catch (SecurityException e) {
2919 Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
2920 Log.e(TAG, "Launcher does not have permission to launch settings");
2921 } catch (ActivityNotFoundException e) {
2922 Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
2923 Log.e(TAG, "Unable to launch settings");
2924 }
2925 }
2926
2927 // returns true if the activity was started
2928 boolean startApplicationUninstallActivity(ComponentName componentName, int flags,
2929 UserHandleCompat user) {
2930 if ((flags & AppInfo.DOWNLOADED_FLAG) == 0) {
2931 // System applications cannot be installed. For now, show a toast explaining that.
2932 // We may give them the option of disabling apps this way.
2933 int messageId = R.string.uninstall_system_app_text;
2934 Toast.makeText(this, messageId, Toast.LENGTH_SHORT).show();
2935 return false;
2936 } else {
2937 String packageName = componentName.getPackageName();
2938 String className = componentName.getClassName();
2939 Intent intent = new Intent(
2940 Intent.ACTION_DELETE, Uri.fromParts("package", packageName, className));
2941 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
2942 Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
2943 if (user != null) {
2944 user.addToIntent(intent, Intent.EXTRA_USER);
2945 }
2946 startActivity(intent);
2947 return true;
2948 }
2949 }
2950
2951 boolean startActivity(View v, Intent intent, Object tag) {
2952 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
2953 try {
2954 // Only launch using the new animation if the shortcut has not opted out (this is a
2955 // private contract between launcher and may be ignored in the future).
2956 boolean useLaunchAnimation = (v != null) &&
2957 !intent.hasExtra(INTENT_EXTRA_IGNORE_LAUNCH_ANIMATION);
2958 LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(this);
2959 UserManagerCompat userManager = UserManagerCompat.getInstance(this);
2960
2961 UserHandleCompat user = null;
2962 if (intent.hasExtra(AppInfo.EXTRA_PROFILE)) {
2963 long serialNumber = intent.getLongExtra(AppInfo.EXTRA_PROFILE, -1);
2964 user = userManager.getUserForSerialNumber(serialNumber);
2965 }
2966
2967 Bundle optsBundle = null;
2968 <<<<<<< GitAnalyzerPlus_ours
2969 if (useLaunchAnimation) {
2970 ActivityOptions opts = null;
2971 if (sClipRevealMethod != null) {
2972 // TODO: call method directly when Launcher3 can depend on M APIs
2973 int left = 0, top = 0;
2974 int width = v.getMeasuredWidth(), height = v.getMeasuredHeight();
2975 if (v instanceof TextView) {
2976 // Launch from center of icon, not entire view
2977 Drawable icon = Workspace.getTextViewIcon((TextView) v);
2978 if (icon != null) {
2979 Rect bounds = icon.getBounds();
2980 left = (width - bounds.width()) / 2;
2981 top = v.getPaddingTop();
2982 width = bounds.width();
2983 height = bounds.height();
2984 }
2985 }
2986 try {
2987 opts = (ActivityOptions) sClipRevealMethod.invoke(null, v,
2988 left, top, width, height);
2989 } catch (IllegalAccessException e) {
2990 Log.d(TAG, "Could not call makeClipRevealAnimation: " + e);
2991 sClipRevealMethod = null;
2992 } catch (InvocationTargetException e) {
2993 Log.d(TAG, "Could not call makeClipRevealAnimation: " + e);
2994 sClipRevealMethod = null;
2995 }
2996 }
2997 if (opts == null && !Utilities.isLmpOrAbove()) {
2998 opts = ActivityOptions.makeScaleUpAnimation(v, 0, 0,
2999 v.getMeasuredWidth(), v.getMeasuredHeight());
3000 }
3001 optsBundle = opts != null ? opts.toBundle() : null;
3002 ||||||| GitAnalyzerPlus_base
3003 if (useLaunchAnimation && !Utilities.isLmpOrAbove()) {
3004 // On pre-L devices, we use the scale up transition.
3005 // Otherwise we use system default.
3006 ActivityOptions opts =
3007 ActivityOptions.makeScaleUpAnimation(v, 0, 0, v.getMeasuredWidth(), v.getMeasured🔵
3008 optsBundle = opts.toBundle();
3009 }
3010
3011 if (user == null || user.equals(UserHandleCompat.myUserHandle())) {
3012 // Could be launching some bookkeeping activity
3013 startActivity(intent, optsBundle);
3014 } else {
3015 // TODO Component can be null when shortcuts are supported for secondary user
3016 launcherApps.startActivityForProfile(intent.getComponent(), user,
3017 intent.getSourceBounds(), optsBundle);
3018 }
3019 return true;
3020 } catch (SecurityException e) {
3021 Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
3022 Log.e(TAG, "Launcher does not have the permission to launch " + intent +
3023 ". Make sure to create a MAIN intent-filter for the corresponding activity " +
3024 "or use the exported attribute for this activity. "
3025 + "tag="+ tag + " intent=" + intent, e);
3026 }
3027 return false;
3028 }
3029
3030 boolean startActivitySafely(View v, Intent intent, Object tag) {
3031 boolean success = false;
3032 if (mIsSafeModeEnabled && !Utilities.isSystemApp(this, intent)) {
3033 Toast.makeText(this, R.string.safemode_shortcut_error, Toast.LENGTH_SHORT).show();
3034 return false;
3035 }
3036 try {
3037 success = startActivity(v, intent, tag);
3038 } catch (ActivityNotFoundException e) {
3039 Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
3040 Log.e(TAG, "Unable to launch. tag=" + tag + " intent=" + intent, e);
3041 }
3042 return success;
3043 }
3044
3045 /**
3046 * This method draws the FolderIcon to an ImageView and then adds and positions that ImageView
3047 * in the DragLayer in the exact absolute location of the original FolderIcon.
3048 */
3049 private void copyFolderIconToImage(FolderIcon fi) {
3050 final int width = fi.getMeasuredWidth();
3051 final int height = fi.getMeasuredHeight();
3052
3053 // Lazy load ImageView, Bitmap and Canvas
3054 if (mFolderIconImageView == null) {
3055 mFolderIconImageView = new ImageView(this);
3056 }
3057 if (mFolderIconBitmap == null || mFolderIconBitmap.getWidth() != width ||
3058 mFolderIconBitmap.getHeight() != height) {
3059 mFolderIconBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
3060 =======
3061 if (useLaunchAnimation && !Utilities.isLmpOrAbove()) {
3062 // On pre-L devices, we use the scale up transition.
3063 ActivityOptions opts =
3064 ActivityOptions.makeScaleUpAnimation(v, 0, 0, v.getMeasuredWidth(), v.getMeasured🔵
3065 optsBundle = opts.toBundle();
3066 } else if (useLaunchAnimation && Utilities.isLmpMr1()) {
3067 // On L-MR1 devices, we use custom slide up animation without a delay
3068 // On L devices, we use the system default slide up.
3069 ActivityOptions opts = ActivityOptions.makeCustomAnimation(this,
3070 R.anim.task_open_enter, R.anim.no_anim);
3071 optsBundle = opts.toBundle();
3072 >>>>>>> GitAnalyzerPlus_theirs
3073 }
3074
3075 if (user == null || user.equals(UserHandleCompat.myUserHandle())) {
3076 // Could be launching some bookkeeping activity
3077 startActivity(intent, optsBundle);
3078 } else {
3079 // TODO Component can be null when shortcuts are supported for secondary user
3080 launcherApps.startActivityForProfile(intent.getComponent(), user,
3081 intent.getSourceBounds(), optsBundle);
3082 }
3083 return true;
3084 } catch (SecurityException e) {
3085 Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
3086 Log.e(TAG, "Launcher does not have the permission to launch " + intent +
3087 ". Make sure to create a MAIN intent-filter for the corresponding activity " +
3088 "or use the exported attribute for this activity. "
3089 + "tag="+ tag + " intent=" + intent, e);
3090 }
3091 return false;
3092 }
3093
3094 boolean startActivitySafely(View v, Intent intent, Object tag) {
3095 boolean success = false;
3096 if (mIsSafeModeEnabled && !Utilities.isSystemApp(this, intent)) {
3097 Toast.makeText(this, R.string.safemode_shortcut_error, Toast.LENGTH_SHORT).show();
3098 return false;
3099 }
3100 try {
3101 success = startActivity(v, intent, tag);
3102 } catch (ActivityNotFoundException e) {
3103 Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
3104 Log.e(TAG, "Unable to launch. tag=" + tag + " intent=" + intent, e);
3105 }
3106 return success;
3107 }
3108
3109 /**
3110 * This method draws the FolderIcon to an ImageView and then adds and positions that ImageView
3111 * in the DragLayer in the exact absolute location of the original FolderIcon.
3112 */
3113 private void copyFolderIconToImage(FolderIcon fi) {
3114 final int width = fi.getMeasuredWidth();
3115 final int height = fi.getMeasuredHeight();
3116
3117 // Lazy load ImageView, Bitmap and Canvas
3118 if (mFolderIconImageView == null) {
3119 mFolderIconImageView = new ImageView(this);
3120 }
3121 if (mFolderIconBitmap == null || mFolderIconBitmap.getWidth() != width ||
3122 mFolderIconBitmap.getHeight() != height) {
3123 mFolderIconBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
3124 mFolderIconCanvas = new Canvas(mFolderIconBitmap);
3125 }
3126
3127 DragLayer.LayoutParams lp;
3128 if (mFolderIconImageView.getLayoutParams() instanceof DragLayer.LayoutParams) {
3129 lp = (DragLayer.LayoutParams) mFolderIconImageView.getLayoutParams();
3130 } else {
3131 lp = new DragLayer.LayoutParams(width, height);
3132 }
3133
3134 // The layout from which the folder is being opened may be scaled, adjust the starting
3135 // view size by this scale factor.
3136 float scale = mDragLayer.getDescendantRectRelativeToSelf(fi, mRectForFolderAnimation);
3137 lp.customPosition = true;
3138 lp.x = mRectForFolderAnimation.left;
3139 lp.y = mRectForFolderAnimation.top;
3140 lp.width = (int) (scale * width);
3141 lp.height = (int) (scale * height);
3142
3143 mFolderIconCanvas.drawColor(0, PorterDuff.Mode.CLEAR);
3144 fi.draw(mFolderIconCanvas);
3145 mFolderIconImageView.setImageBitmap(mFolderIconBitmap);
3146 if (fi.getFolder() != null) {
3147 mFolderIconImageView.setPivotX(fi.getFolder().getPivotXForIconAnimation());
3148 mFolderIconImageView.setPivotY(fi.getFolder().getPivotYForIconAnimation());
3149 }
3150 // Just in case this image view is still in the drag layer from a previous animation,
3151 // we remove it and re-add it.
3152 if (mDragLayer.indexOfChild(mFolderIconImageView) != -1) {
3153 mDragLayer.removeView(mFolderIconImageView);
3154 }
3155 mDragLayer.addView(mFolderIconImageView, lp);
3156 if (fi.getFolder() != null) {
3157 fi.getFolder().bringToFront();
3158 }
3159 }
3160
3161 private void growAndFadeOutFolderIcon(FolderIcon fi) {
3162 if (fi == null) return;
3163 PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 0);
3164 PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 1.5f);
3165 PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 1.5f);
3166
3167 FolderInfo info = (FolderInfo) fi.getTag();
3168 if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
3169 CellLayout cl = (CellLayout) fi.getParent().getParent();
3170 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) fi.getLayoutParams();
3171 cl.setFolderLeaveBehindCell(lp.cellX, lp.cellY);
3172 }
3173
3174 // Push an ImageView copy of the FolderIcon into the DragLayer and hide the original
3175 copyFolderIconToImage(fi);
3176 fi.setVisibility(View.INVISIBLE);
3177
3178 ObjectAnimator oa = LauncherAnimUtils.ofPropertyValuesHolder(mFolderIconImageView, alpha,
3179 scaleX, scaleY);
3180 if (Utilities.isLmpOrAbove()) {
3181 oa.setInterpolator(new LogDecelerateInterpolator(100, 0));
3182 }
3183 oa.setDuration(getResources().getInteger(R.integer.config_folderExpandDuration));
3184 oa.start();
3185 }
3186
3187 private void shrinkAndFadeInFolderIcon(final FolderIcon fi) {
3188 if (fi == null) return;
3189 PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 1.0f);
3190 PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 1.0f);
3191 PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 1.0f);
3192
3193 final CellLayout cl = (CellLayout) fi.getParent().getParent();
3194
3195 // We remove and re-draw the FolderIcon in-case it has changed
3196 mDragLayer.removeView(mFolderIconImageView);
3197 copyFolderIconToImage(fi);
3198 ObjectAnimator oa = LauncherAnimUtils.ofPropertyValuesHolder(mFolderIconImageView, alpha,
3199 scaleX, scaleY);
3200 oa.setDuration(getResources().getInteger(R.integer.config_folderExpandDuration));
3201 oa.addListener(new AnimatorListenerAdapter() {
3202 @Override
3203 public void onAnimationEnd(Animator animation) {
3204 if (cl != null) {
3205 cl.clearFolderLeaveBehind();
3206 // Remove the ImageView copy of the FolderIcon and make the original visible.
3207 mDragLayer.removeView(mFolderIconImageView);
3208 fi.setVisibility(View.VISIBLE);
3209 }
3210 }
3211 });
3212 oa.start();
3213 }
3214
3215 /**
3216 * Opens the user folder described by the specified tag. The opening of the folder
3217 * is animated relative to the specified View. If the View is null, no animation
3218 * is played.
3219 *
3220 * @param folderInfo The FolderInfo describing the folder to open.
3221 */
3222 public void openFolder(FolderIcon folderIcon) {
3223 Folder folder = folderIcon.getFolder();
3224 Folder openFolder = mWorkspace != null ? mWorkspace.getOpenFolder() : null;
3225 if (openFolder != null && openFolder != folder) {
3226 // Close any open folder before opening a folder.
3227 closeFolder();
3228 }
3229
3230 FolderInfo info = folder.mInfo;
3231
3232 info.opened = true;
3233
3234 // While the folder is open, the position of the icon cannot change.
3235 ((CellLayout.LayoutParams) folderIcon.getLayoutParams()).canReorder = false;
3236
3237 // Just verify that the folder hasn't already been added to the DragLayer.
3238 // There was a one-off crash where the folder had a parent already.
3239 if (folder.getParent() == null) {
3240 mDragLayer.addView(folder);
3241 mDragController.addDropTarget((DropTarget) folder);
3242 } else {
3243 Log.w(TAG, "Opening folder (" + folder + ") which already has a parent (" +
3244 folder.getParent() + ").");
3245 }
3246 folder.animateOpen();
3247 growAndFadeOutFolderIcon(folderIcon);
3248
3249 // Notify the accessibility manager that this folder "window" has appeared and occluded
3250 // the workspace items
3251 folder.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
3252 getDragLayer().sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
3253 }
3254
3255 public void closeFolder() {
3256 Folder folder = mWorkspace != null ? mWorkspace.getOpenFolder() : null;
3257 if (folder != null) {
3258 if (folder.isEditingName()) {
3259 folder.dismissEditingName();
3260 }
3261 closeFolder(folder);
3262 }
3263 }
3264
3265 public void closeFolder(Folder folder) {
3266 folder.getInfo().opened = false;
3267
3268 ViewGroup parent = (ViewGroup) folder.getParent().getParent();
3269 if (parent != null) {
3270 FolderIcon fi = (FolderIcon) mWorkspace.getViewForTag(folder.mInfo);
3271 shrinkAndFadeInFolderIcon(fi);
3272 if (fi != null) {
3273 ((CellLayout.LayoutParams) fi.getLayoutParams()).canReorder = true;
3274 }
3275 }
3276 folder.animateClosed();
3277
3278 // Notify the accessibility manager that this folder "window" has disappeard and no
3279 // longer occludeds the workspace items
3280 getDragLayer().sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
3281 }
3282
3283 public boolean onLongClick(View v) {
3284 if (!isDraggingEnabled()) return false;
3285 if (isWorkspaceLocked()) return false;
3286 if (mState != State.WORKSPACE) return false;
3287
3288 if (v instanceof Workspace) {
3289 if (!mWorkspace.isInOverviewMode()) {
3290 if (!mWorkspace.isTouchActive()) {
3291 showOverviewMode(true);
3292 mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
3293 HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
3294 return true;
3295 } else {
3296 return false;
3297 }
3298 } else {
3299 return false;
3300 }
3301 }
3302
3303 CellLayout.CellInfo longClickCellInfo = null;
3304 View itemUnderLongClick = null;
3305 if (v.getTag() instanceof ItemInfo) {
3306 ItemInfo info = (ItemInfo) v.getTag();
3307 longClickCellInfo = new CellLayout.CellInfo(v, info);
3308 itemUnderLongClick = longClickCellInfo.cell;
3309 resetAddInfo();
3310 }
3311
3312 // The hotseat touch handling does not go through Workspace, and we always allow long press
3313 // on hotseat items.
3314 final boolean inHotseat = isHotseatLayout(v);
3315 if (!mDragController.isDragging()) {
3316 if (itemUnderLongClick == null) {
3317 // User long pressed on empty space
3318 mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
3319 HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
3320 if (mWorkspace.isInOverviewMode()) {
3321 mWorkspace.startReordering(v);
3322 } else {
3323 showOverviewMode(true);
3324 }
3325 } else {
3326 final boolean isAllAppsButton = inHotseat && isAllAppsButtonRank(
3327 mHotseat.getOrderInHotseat(
3328 longClickCellInfo.cellX,
3329 longClickCellInfo.cellY));
3330 if (!(itemUnderLongClick instanceof Folder || isAllAppsButton)) {
3331 // User long pressed on an item
3332 mWorkspace.startDrag(longClickCellInfo);
3333 }
3334 }
3335 }
3336 return true;
3337 }
3338
3339 boolean isHotseatLayout(View layout) {
3340 return mHotseat != null && layout != null &&
3341 (layout instanceof CellLayout) && (layout == mHotseat.getLayout());
3342 }
3343
3344 /**
3345 * Returns the CellLayout of the specified container at the specified screen.
3346 */
3347 public CellLayout getCellLayout(long container, long screenId) {
3348 if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
3349 if (mHotseat != null) {
3350 return mHotseat.getLayout();
3351 } else {
3352 return null;
3353 }
3354 } else {
3355 return mWorkspace.getScreenWithId(screenId);
3356 }
3357 }
3358
3359 /**
3360 * For overridden classes.
3361 */
3362 public boolean isAllAppsVisible() {
3363 return isAppsViewVisible();
3364 }
3365
3366 public boolean isAppsViewVisible() {
3367 return (mState == State.APPS) || (mOnResumeState == State.APPS);
3368 }
3369
3370 public boolean isWidgetsViewVisible() {
3371 return (mState == State.WIDGETS) || (mOnResumeState == State.WIDGETS);
3372 }
3373
3374 private void setWorkspaceBackground(int background) {
3375 switch (background) {
3376 case WORKSPACE_BACKGROUND_TRANSPARENT:
3377 getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
3378 break;
3379 case WORKSPACE_BACKGROUND_BLACK:
3380 getWindow().setBackgroundDrawable(null);
3381 break;
3382 default:
3383 getWindow().setBackgroundDrawable(mWorkspaceBackgroundDrawable);
3384 }
3385 }
3386
3387 protected void changeWallpaperVisiblity(boolean visible) {
3388 int wpflags = visible ? WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER : 0;
3389 int curflags = getWindow().getAttributes().flags
3390 & WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
3391 if (wpflags != curflags) {
3392 getWindow().setFlags(wpflags, WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER);
3393 }
3394 setWorkspaceBackground(visible ? WORKSPACE_BACKGROUND_GRADIENT : WORKSPACE_BACKGROUND_BLACK);
3395 }
3396
3397 @Override
3398 public void onTrimMemory(int level) {
3399 super.onTrimMemory(level);
3400 if (level >= ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
3401 // The widget preview db can result in holding onto over
3402 // 3MB of memory for caching which isn't necessary.
3403 SQLiteDatabase.releaseMemory();
3404
3405 // This clears all widget bitmaps from the widget tray
3406 // TODO(hyunyoungs)
3407 }
3408 if (mLauncherCallbacks != null) {
3409 mLauncherCallbacks.onTrimMemory(level);
3410 }
3411 }
3412
3413 @Override
3414 public void onStateTransitionHideSearchBar() {
3415 // Hide the search bar
3416 if (mSearchDropTargetBar != null) {
3417 mSearchDropTargetBar.hideSearchBar(false /* animated */);
3418 }
3419 }
3420
3421 public void showWorkspace(boolean animated) {
3422 showWorkspace(WorkspaceStateTransitionAnimation.SCROLL_TO_CURRENT_PAGE, animated, null,
3423 true);
3424 }
3425
3426 public void showWorkspace(boolean animated, Runnable onCompleteRunnable) {
3427 showWorkspace(WorkspaceStateTransitionAnimation.SCROLL_TO_CURRENT_PAGE, animated,
3428 onCompleteRunnable, true);
3429 }
3430
3431 protected void showWorkspace(int snapToPage, boolean animated) {
3432 showWorkspace(snapToPage, animated, null, true);
3433 }
3434
3435 void showWorkspace(int snapToPage, boolean animated, Runnable onCompleteRunnable,
3436 boolean notifyLauncherCallbacks) {
3437 boolean changed = mState != State.WORKSPACE ||
3438 mWorkspace.getState() != Workspace.State.NORMAL;
3439 if (changed) {
3440 boolean wasInSpringLoadedMode = (mState != State.WORKSPACE);
3441 mWorkspace.setVisibility(View.VISIBLE);
3442 mStateTransitionAnimation.startAnimationToWorkspace(mState, Workspace.State.NORMAL,
3443 snapToPage, animated, onCompleteRunnable);
3444
3445 // Show the search bar (only animate if we were showing the drop target bar in spring
3446 // loaded mode)
3447 if (mSearchDropTargetBar != null) {
3448 mSearchDropTargetBar.showSearchBar(animated && wasInSpringLoadedMode);
3449 }
3450
3451 // Set focus to the AppsCustomize button
3452 if (mAllAppsButton != null) {
3453 mAllAppsButton.requestFocus();
3454 }
3455 }
3456
3457 // Change the state *after* we've called all the transition code
3458 mState = State.WORKSPACE;
3459
3460 // Resume the auto-advance of widgets
3461 mUserPresent = true;
3462 updateAutoAdvanceState();
3463
3464 if (changed) {
3465 // Send an accessibility event to announce the context change
3466 getWindow().getDecorView()
3467 .sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
3468 if (notifyLauncherCallbacks) {
3469 // Dismiss all apps when the workspace is shown
3470 if (!Launcher.DISABLE_ALL_APPS_SEARCH_INTEGRATION && mLauncherCallbacks != null) {
3471 mLauncherCallbacks.onAllAppsHidden();
3472 }
3473 }
3474 }
3475 }
3476
3477 void showOverviewMode(boolean animated) {
3478 mWorkspace.setVisibility(View.VISIBLE);
3479 mStateTransitionAnimation.startAnimationToWorkspace(mState, Workspace.State.OVERVIEW,
3480 WorkspaceStateTransitionAnimation.SCROLL_TO_CURRENT_PAGE, animated,
3481 null /* onCompleteRunnable */);
3482 mState = State.WORKSPACE;
3483 }
3484
3485 /**
3486 * Shows the apps view.
3487 */
3488 void showAppsView(boolean animated, boolean resetListToTop, boolean updatePredictedApps) {
3489 if (resetListToTop) {
3490 mAppsView.scrollToTop();
3491 }
3492 if (updatePredictedApps) {
3493 tryAndUpdatePredictedApps();
3494 }
3495 showAppsOrWidgets(animated, State.APPS);
3496 }
3497
3498 /**
3499 * Shows the widgets view.
3500 */
3501 void showWidgetsView(boolean animated, boolean resetPageToZero) {
3502 if (LOGD) Log.d(TAG, "showWidgetsView:" + animated + " resetPageToZero:" + resetPageToZero);
3503 if (resetPageToZero) {
3504 mWidgetsView.scrollToTop();
3505 }
3506 showAppsOrWidgets(animated, State.WIDGETS);
3507
3508 mWidgetsView.post(new Runnable() {
3509 @Override
3510 public void run() {
3511 mWidgetsView.requestFocus();
3512 }
3513 });
3514 }
3515
3516 /**
3517 * Sets up the transition to show the apps/widgets view.
3518 *
3519 * @return whether the current from and to state allowed this operation
3520 */
3521 // TODO: calling method should use the return value so that when {@code false} is returned
3522 // the workspace transition doesn't fall into invalid state.
3523 private boolean showAppsOrWidgets(boolean animated, State toState) {
3524 if (mState != State.WORKSPACE && mState != State.APPS_SPRING_LOADED &&
3525 mState != State.WIDGETS_SPRING_LOADED) {
3526 return false;
3527 }
3528 if (toState != State.APPS && toState != State.WIDGETS) {
3529 return false;
3530 }
3531
3532 if (toState == State.APPS) {
3533 mStateTransitionAnimation.startAnimationToAllApps(animated);
3534 if (!Launcher.DISABLE_ALL_APPS_SEARCH_INTEGRATION && mLauncherCallbacks != null) {
3535 mLauncherCallbacks.onAllAppsShown();
3536 }
3537 } else {
3538 mStateTransitionAnimation.startAnimationToWidgets(animated);
3539 }
3540
3541 // Change the state *after* we've called all the transition code
3542 mState = toState;
3543
3544 // Pause the auto-advance of widgets until we are out of AllApps
3545 mUserPresent = false;
3546 updateAutoAdvanceState();
3547 closeFolder();
3548
3549 // Send an accessibility event to announce the context change
3550 getWindow().getDecorView()
3551 .sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
3552 return true;
3553 }
3554
3555 /**
3556 * Updates the workspace and interaction state on state change, and return the animation to this
3557 * new state.
3558 */
3559 public Animator startWorkspaceStateChangeAnimation(Workspace.State toState, int toPage,
3560 boolean animated, HashMap<View, Integer> layerViews) {
3561 Workspace.State fromState = mWorkspace.getState();
3562 Animator anim = mWorkspace.setStateWithAnimation(toState, toPage, animated, layerViews);
3563 updateInteraction(fromState, toState);
3564 return anim;
3565 }
3566
3567 public void enterSpringLoadedDragMode() {
3568 if (LOGD) Log.d(TAG, String.format("enterSpringLoadedDragMode [mState=%s", mState.name()));
3569 if (mState == State.WORKSPACE || mState == State.APPS_SPRING_LOADED ||
3570 mState == State.WIDGETS_SPRING_LOADED) {
3571 return;
3572 }
3573
3574 mStateTransitionAnimation.startAnimationToWorkspace(mState, Workspace.State.SPRING_LOADED,
3575 WorkspaceStateTransitionAnimation.SCROLL_TO_CURRENT_PAGE, true /* animated */,
3576 null /* onCompleteRunnable */);
3577 mState = isAppsViewVisible() ? State.APPS_SPRING_LOADED : State.WIDGETS_SPRING_LOADED;
3578 }
3579
3580 public void exitSpringLoadedDragModeDelayed(final boolean successfulDrop, int delay,
3581 final Runnable onCompleteRunnable) {
3582 if (mState != State.APPS_SPRING_LOADED && mState != State.WIDGETS_SPRING_LOADED) return;
3583
3584 if (successfulDrop) {
3585 // We need to trigger all apps hidden to notify search to update itself before the
3586 // delayed call to showWorkspace below
3587 if (!Launcher.DISABLE_ALL_APPS_SEARCH_INTEGRATION && mLauncherCallbacks != null) {
3588 mLauncherCallbacks.onAllAppsHidden();
3589 }
3590 }
3591
3592 mHandler.postDelayed(new Runnable() {
3593 @Override
3594 public void run() {
3595 if (successfulDrop) {
3596 // TODO(hyunyoungs): verify if this hack is still needed, if not, delete.
3597 //
3598 // Before we show workspace, hide all apps again because
3599 // exitSpringLoadedDragMode made it visible. This is a bit hacky; we should
3600 // clean up our state transition functions
3601 mWidgetsView.setVisibility(View.GONE);
3602 showWorkspace(true, onCompleteRunnable);
3603 } else {
3604 exitSpringLoadedDragMode();
3605 }
3606 }
3607 }, delay);
3608 }
3609
3610 void exitSpringLoadedDragMode() {
3611 if (mState == State.APPS_SPRING_LOADED) {
3612 showAppsView(true /* animated */, false /* resetListToTop */,
3613 false /* updatePredictedApps */);
3614 } else if (mState == State.WIDGETS_SPRING_LOADED) {
3615 showWidgetsView(true, false);
3616 }
3617 }
3618
3619 /**
3620 * Updates the set of predicted apps if it hasn't been updated since the last time Launcher was
3621 * resumed.
3622 */
3623 private void tryAndUpdatePredictedApps() {
3624 if (mLauncherCallbacks != null) {
3625 List<ComponentName> apps = mLauncherCallbacks.getPredictedApps();
3626 if (!apps.isEmpty()) {
3627 mAppsView.setPredictedApps(apps);
3628 }
3629 }
3630 }
3631
3632 void lockAllApps() {
3633 // TODO
3634 }
3635
3636 void unlockAllApps() {
3637 // TODO
3638 }
3639
3640 protected void disableVoiceButtonProxy(boolean disable) {
3641 // NO-OP
3642 }
3643
3644 public View getOrCreateQsbBar() {
3645 if (mLauncherCallbacks != null && mLauncherCallbacks.providesSearch()) {
3646 return mLauncherCallbacks.getQsbBar();
3647 }
3648
3649 if (mQsb == null) {
3650 AppWidgetProviderInfo searchProvider = Utilities.getSearchWidgetProvider(this);
3651 if (searchProvider == null) {
3652 return null;
3653 }
3654
3655 Bundle opts = new Bundle();
3656 opts.putInt(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY,
3657 AppWidgetProviderInfo.WIDGET_CATEGORY_SEARCHBOX);
3658
3659 SharedPreferences sp = getSharedPreferences(
3660 LauncherAppState.getSharedPreferencesKey(), MODE_PRIVATE);
3661 int widgetId = sp.getInt(QSB_WIDGET_ID, -1);
3662 AppWidgetProviderInfo widgetInfo = mAppWidgetManager.getAppWidgetInfo(widgetId);
3663 if (!searchProvider.provider.flattenToString().equals(
3664 sp.getString(QSB_WIDGET_PROVIDER, null))
3665 || (widgetInfo == null)
3666 || !widgetInfo.provider.equals(searchProvider.provider)) {
3667 // A valid widget is not already bound.
3668 if (widgetId > -1) {
3669 mAppWidgetHost.deleteAppWidgetId(widgetId);
3670 widgetId = -1;
3671 }
3672
3673 // Try to bind a new widget
3674 widgetId = mAppWidgetHost.allocateAppWidgetId();
3675
3676 if (!AppWidgetManagerCompat.getInstance(this)
3677 .bindAppWidgetIdIfAllowed(widgetId, searchProvider, opts)) {
3678 mAppWidgetHost.deleteAppWidgetId(widgetId);
3679 widgetId = -1;
3680 }
3681
3682 sp.edit()
3683 .putInt(QSB_WIDGET_ID, widgetId)
3684 .putString(QSB_WIDGET_PROVIDER, searchProvider.provider.flattenToString())
3685 .commit();
3686 }
3687
3688 if (widgetId != -1) {
3689 mQsb = mAppWidgetHost.createView(this, widgetId, searchProvider);
3690 mQsb.updateAppWidgetOptions(opts);
3691 mQsb.setPadding(0, 0, 0, 0);
3692 mSearchDropTargetBar.addView(mQsb);
3693 mSearchDropTargetBar.setQsbSearchBar(mQsb);
3694 }
3695 }
3696 return mQsb;
3697 }
3698
3699 private void reinflateQSBIfNecessary() {
3700 if (mQsb instanceof LauncherAppWidgetHostView &&
3701 ((LauncherAppWidgetHostView) mQsb).isReinflateRequired()) {
3702 mSearchDropTargetBar.removeView(mQsb);
3703 mQsb = null;
3704 mSearchDropTargetBar.setQsbSearchBar(getOrCreateQsbBar());
3705 }
3706 }
3707
3708 @Override
3709 public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
3710 final boolean result = super.dispatchPopulateAccessibilityEvent(event);
3711 final List<CharSequence> text = event.getText();
3712 text.clear();
3713 // Populate event with a fake title based on the current state.
3714 if (mState == State.APPS) {
3715 text.add(getString(R.string.all_apps_button_label));
3716 } else if (mState == State.WIDGETS) {
3717 text.add(getString(R.string.widget_button_text));
3718 } else {
3719 text.add(getString(R.string.all_apps_home_button_label));
3720 }
3721 return result;
3722 }
3723
3724 /**
3725 * Receives notifications when system dialogs are to be closed.
3726 */
3727 @Thunk class CloseSystemDialogsIntentReceiver extends BroadcastReceiver {
3728 @Override
3729 public void onReceive(Context context, Intent intent) {
3730 closeSystemDialogs();
3731 }
3732 }
3733
3734 /**
3735 * Receives notifications whenever the appwidgets are reset.
3736 */
3737 private class AppWidgetResetObserver extends ContentObserver {
3738 public AppWidgetResetObserver() {
3739 super(new Handler());
3740 }
3741
3742 @Override
3743 public void onChange(boolean selfChange) {
3744 onAppWidgetReset();
3745 }
3746 }
3747
3748 /**
3749 * If the activity is currently paused, signal that we need to run the passed Runnable
3750 * in onResume.
3751 *
3752 * This needs to be called from incoming places where resources might have been loaded
3753 * while the activity is paused. That is because the Configuration (e.g., rotation) might be
3754 * wrong when we're not running, and if the activity comes back to what the configuration was
3755 * when we were paused, activity is not restarted.
3756 *
3757 * Implementation of the method from LauncherModel.Callbacks.
3758 *
3759 * @return {@code true} if we are currently paused. The caller might be able to skip some work
3760 */
3761 private boolean waitUntilResume(Runnable run, boolean deletePreviousRunnables) {
3762 if (mPaused) {
3763 if (LOGD) Log.d(TAG, "Deferring update until onResume");
3764 if (deletePreviousRunnables) {
3765 while (mBindOnResumeCallbacks.remove(run)) {
3766 }
3767 }
3768 mBindOnResumeCallbacks.add(run);
3769 return true;
3770 } else {
3771 return false;
3772 }
3773 }
3774
3775 private boolean waitUntilResume(Runnable run) {
3776 return waitUntilResume(run, false);
3777 }
3778
3779 public void addOnResumeCallback(Runnable run) {
3780 mOnResumeCallbacks.add(run);
3781 }
3782
3783 /**
3784 * If the activity is currently paused, signal that we need to re-run the loader
3785 * in onResume.
3786 *
3787 * This needs to be called from incoming places where resources might have been loaded
3788 * while we are paused. That is becaues the Configuration might be wrong
3789 * when we're not running, and if it comes back to what it was when we
3790 * were paused, we are not restarted.
3791 *
3792 * Implementation of the method from LauncherModel.Callbacks.
3793 *
3794 * @return true if we are currently paused. The caller might be able to
3795 * skip some work in that case since we will come back again.
3796 */
3797 public boolean setLoadOnResume() {
3798 if (mPaused) {
3799 if (LOGD) Log.d(TAG, "setLoadOnResume");
3800 mOnResumeNeedsLoad = true;
3801 return true;
3802 } else {
3803 return false;
3804 }
3805 }
3806
3807 /**
3808 * Implementation of the method from LauncherModel.Callbacks.
3809 */
3810 public int getCurrentWorkspaceScreen() {
3811 if (mWorkspace != null) {
3812 return mWorkspace.getCurrentPage();
3813 } else {
3814 return SCREEN_COUNT / 2;
3815 }
3816 }
3817
3818 /**
3819 * Refreshes the shortcuts shown on the workspace.
3820 *
3821 * Implementation of the method from LauncherModel.Callbacks.
3822 */
3823 public void startBinding() {
3824 setWorkspaceLoading(true);
3825
3826 // If we're starting binding all over again, clear any bind calls we'd postponed in
3827 // the past (see waitUntilResume) -- we don't need them since we're starting binding
3828 // from scratch again
3829 mBindOnResumeCallbacks.clear();
3830
3831 // Clear the workspace because it's going to be rebound
3832 mWorkspace.clearDropTargets();
3833 mWorkspace.removeAllWorkspaceScreens();
3834
3835 mWidgetsToAdvance.clear();
3836 if (mHotseat != null) {
3837 mHotseat.resetLayout();
3838 }
3839 }
3840
3841 @Override
3842 public void bindScreens(ArrayList<Long> orderedScreenIds) {
3843 bindAddScreens(orderedScreenIds);
3844
3845 // If there are no screens, we need to have an empty screen
3846 if (orderedScreenIds.size() == 0) {
3847 mWorkspace.addExtraEmptyScreen();
3848 }
3849
3850 // Create the custom content page (this call updates mDefaultScreen which calls
3851 // setCurrentPage() so ensure that all pages are added before calling this).
3852 if (hasCustomContentToLeft()) {
3853 mWorkspace.createCustomContentContainer();
3854 populateCustomContentContainer();
3855 }
3856 }
3857
3858 @Override
3859 public void bindAddScreens(ArrayList<Long> orderedScreenIds) {
3860 // Log to disk
3861 Launcher.addDumpLog(TAG, "11683562 - bindAddScreens()", true);
3862 Launcher.addDumpLog(TAG, "11683562 - orderedScreenIds: " +
3863 TextUtils.join(", ", orderedScreenIds), true);
3864 int count = orderedScreenIds.size();
3865 for (int i = 0; i < count; i++) {
3866 mWorkspace.insertNewWorkspaceScreenBeforeEmptyScreen(orderedScreenIds.get(i));
3867 }
3868 }
3869
3870 private boolean shouldShowWeightWatcher() {
3871 String spKey = LauncherAppState.getSharedPreferencesKey();
3872 SharedPreferences sp = getSharedPreferences(spKey, Context.MODE_PRIVATE);
3873 boolean show = sp.getBoolean(SHOW_WEIGHT_WATCHER, SHOW_WEIGHT_WATCHER_DEFAULT);
3874
3875 return show;
3876 }
3877
3878 private void toggleShowWeightWatcher() {
3879 String spKey = LauncherAppState.getSharedPreferencesKey();
3880 SharedPreferences sp = getSharedPreferences(spKey, Context.MODE_PRIVATE);
3881 boolean show = sp.getBoolean(SHOW_WEIGHT_WATCHER, true);
3882
3883 show = !show;
3884
3885 SharedPreferences.Editor editor = sp.edit();
3886 editor.putBoolean(SHOW_WEIGHT_WATCHER, show);
3887 editor.commit();
3888
3889 if (mWeightWatcher != null) {
3890 mWeightWatcher.setVisibility(show ? View.VISIBLE : View.GONE);
3891 }
3892 }
3893
3894 public void bindAppsAdded(final ArrayList<Long> newScreens,
3895 final ArrayList<ItemInfo> addNotAnimated,
3896 final ArrayList<ItemInfo> addAnimated,
3897 final ArrayList<AppInfo> addedApps) {
3898 Runnable r = new Runnable() {
3899 public void run() {
3900 bindAppsAdded(newScreens, addNotAnimated, addAnimated, addedApps);
3901 }
3902 };
3903 if (waitUntilResume(r)) {
3904 return;
3905 }
3906
3907 // Add the new screens
3908 if (newScreens != null) {
3909 bindAddScreens(newScreens);
3910 }
3911
3912 // We add the items without animation on non-visible pages, and with
3913 // animations on the new page (which we will try and snap to).
3914 if (addNotAnimated != null && !addNotAnimated.isEmpty()) {
3915 bindItems(addNotAnimated, 0,
3916 addNotAnimated.size(), false);
3917 }
3918 if (addAnimated != null && !addAnimated.isEmpty()) {
3919 bindItems(addAnimated, 0,
3920 addAnimated.size(), true);
3921 }
3922
3923 // Remove the extra empty screen
3924 mWorkspace.removeExtraEmptyScreen(false, false);
3925
3926 if (addedApps != null && mAppsView != null) {
3927 mAppsView.addApps(addedApps);
3928 }
3929 }
3930
3931 /**
3932 * Bind the items start-end from the list.
3933 *
3934 * Implementation of the method from LauncherModel.Callbacks.
3935 */
3936 public void bindItems(final ArrayList<ItemInfo> shortcuts, final int start, final int end,
3937 final boolean forceAnimateIcons) {
3938 Runnable r = new Runnable() {
3939 public void run() {
3940 bindItems(shortcuts, start, end, forceAnimateIcons);
3941 }
3942 };
3943 if (waitUntilResume(r)) {
3944 return;
3945 }
3946
3947 // Get the list of added shortcuts and intersect them with the set of shortcuts here
3948 final AnimatorSet anim = LauncherAnimUtils.createAnimatorSet();
3949 final Collection<Animator> bounceAnims = new ArrayList<Animator>();
3950 final boolean animateIcons = forceAnimateIcons && canRunNewAppsAnimation();
3951 Workspace workspace = mWorkspace;
3952 long newShortcutsScreenId = -1;
3953 for (int i = start; i < end; i++) {
3954 final ItemInfo item = shortcuts.get(i);
3955
3956 // Short circuit if we are loading dock items for a configuration which has no dock
3957 if (item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT &&
3958 mHotseat == null) {
3959 continue;
3960 }
3961
3962 switch (item.itemType) {
3963 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
3964 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
3965 ShortcutInfo info = (ShortcutInfo) item;
3966 View shortcut = createShortcut(info);
3967
3968 /*
3969 * TODO: FIX collision case
3970 */
3971 if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
3972 CellLayout cl = mWorkspace.getScreenWithId(item.screenId);
3973 if (cl != null && cl.isOccupied(item.cellX, item.cellY)) {
3974 View v = cl.getChildAt(item.cellX, item.cellY);
3975 Object tag = v.getTag();
3976 String desc = "Collision while binding workspace item: " + item
3977 + ". Collides with " + tag;
3978 if (LauncherAppState.isDogfoodBuild()) {
3979 throw (new RuntimeException(desc));
3980 } else {
3981 Log.d(TAG, desc);
3982 }
3983 }
3984 }
3985
3986 workspace.addInScreenFromBind(shortcut, item.container, item.screenId, item.cellX,
3987 item.cellY, 1, 1);
3988 if (animateIcons) {
3989 // Animate all the applications up now
3990 shortcut.setAlpha(0f);
3991 shortcut.setScaleX(0f);
3992 shortcut.setScaleY(0f);
3993 bounceAnims.add(createNewAppBounceAnimation(shortcut, i));
3994 newShortcutsScreenId = item.screenId;
3995 }
3996 break;
3997 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
3998 FolderIcon newFolder = FolderIcon.fromXml(R.layout.folder_icon, this,
3999 (ViewGroup) workspace.getChildAt(workspace.getCurrentPage()),
4000 (FolderInfo) item, mIconCache);
4001 workspace.addInScreenFromBind(newFolder, item.container, item.screenId, item.cellX,
4002 item.cellY, 1, 1);
4003 break;
4004 default:
4005 throw new RuntimeException("Invalid Item Type");
4006 }
4007 }
4008
4009 if (animateIcons) {
4010 // Animate to the correct page
4011 if (newShortcutsScreenId > -1) {
4012 long currentScreenId = mWorkspace.getScreenIdForPageIndex(mWorkspace.getNextPage());
4013 final int newScreenIndex = mWorkspace.getPageIndexForScreenId(newShortcutsScreenId);
4014 final Runnable startBounceAnimRunnable = new Runnable() {
4015 public void run() {
4016 anim.playTogether(bounceAnims);
4017 anim.start();
4018 }
4019 };
4020 if (newShortcutsScreenId != currentScreenId) {
4021 // We post the animation slightly delayed to prevent slowdowns
4022 // when we are loading right after we return to launcher.
4023 mWorkspace.postDelayed(new Runnable() {
4024 public void run() {
4025 if (mWorkspace != null) {
4026 mWorkspace.snapToPage(newScreenIndex);
4027 mWorkspace.postDelayed(startBounceAnimRunnable,
4028 NEW_APPS_ANIMATION_DELAY);
4029 }
4030 }
4031 }, NEW_APPS_PAGE_MOVE_DELAY);
4032 } else {
4033 mWorkspace.postDelayed(startBounceAnimRunnable, NEW_APPS_ANIMATION_DELAY);
4034 }
4035 }
4036 }
4037 workspace.requestLayout();
4038 }
4039
4040 /**
4041 * Implementation of the method from LauncherModel.Callbacks.
4042 */
4043 public void bindFolders(final LongArrayMap<FolderInfo> folders) {
4044 Runnable r = new Runnable() {
4045 public void run() {
4046 bindFolders(folders);
4047 }
4048 };
4049 if (waitUntilResume(r)) {
4050 return;
4051 }
4052 sFolders = folders.clone();
4053 }
4054
4055 /**
4056 * Add the views for a widget to the workspace.
4057 *
4058 * Implementation of the method from LauncherModel.Callbacks.
4059 */
4060 public void bindAppWidget(final LauncherAppWidgetInfo item) {
4061 Runnable r = new Runnable() {
4062 public void run() {
4063 bindAppWidget(item);
4064 }
4065 };
4066 if (waitUntilResume(r)) {
4067 return;
4068 }
4069
4070 final long start = DEBUG_WIDGETS ? SystemClock.uptimeMillis() : 0;
4071 if (DEBUG_WIDGETS) {
4072 Log.d(TAG, "bindAppWidget: " + item);
4073 }
4074 final Workspace workspace = mWorkspace;
4075
4076 LauncherAppWidgetProviderInfo appWidgetInfo =
4077 LauncherModel.getProviderInfo(this, item.providerName, item.user);
4078
4079 if (!mIsSafeModeEnabled
4080 && ((item.restoreStatus & LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY) == 0)
4081 && ((item.restoreStatus & LauncherAppWidgetInfo.FLAG_ID_NOT_VALID) != 0)) {
4082 if (appWidgetInfo == null) {
4083 if (DEBUG_WIDGETS) {
4084 Log.d(TAG, "Removing restored widget: id=" + item.appWidgetId
4085 + " belongs to component " + item.providerName
4086 + ", as the povider is null");
4087 }
4088 LauncherModel.deleteItemFromDatabase(this, item);
4089 return;
4090 }
4091 // Note: This assumes that the id remap broadcast is received before this step.
4092 // If that is not the case, the id remap will be ignored and user may see the
4093 // click to setup view.
4094 PendingAddWidgetInfo pendingInfo = new PendingAddWidgetInfo(this, appWidgetInfo, null);
4095 pendingInfo.spanX = item.spanX;
4096 pendingInfo.spanY = item.spanY;
4097 pendingInfo.minSpanX = item.minSpanX;
4098 pendingInfo.minSpanY = item.minSpanY;
4099 Bundle options = null;
4100 WidgetHostViewLoader.getDefaultOptionsForWidget(this, pendingInfo);
4101
4102 int newWidgetId = mAppWidgetHost.allocateAppWidgetId();
4103 boolean success = mAppWidgetManager.bindAppWidgetIdIfAllowed(
4104 newWidgetId, appWidgetInfo, options);
4105
4106 // TODO consider showing a permission dialog when the widget is clicked.
4107 if (!success) {
4108 mAppWidgetHost.deleteAppWidgetId(newWidgetId);
4109 if (DEBUG_WIDGETS) {
4110 Log.d(TAG, "Removing restored widget: id=" + item.appWidgetId
4111 + " belongs to component " + item.providerName
4112 + ", as the launcher is unable to bing a new widget id");
4113 }
4114 LauncherModel.deleteItemFromDatabase(this, item);
4115 return;
4116 }
4117
4118 item.appWidgetId = newWidgetId;
4119
4120 // If the widget has a configure activity, it is still needs to set it up, otherwise
4121 // the widget is ready to go.
4122 item.restoreStatus = (appWidgetInfo.configure == null)
4123 ? LauncherAppWidgetInfo.RESTORE_COMPLETED
4124 : LauncherAppWidgetInfo.FLAG_UI_NOT_READY;
4125
4126 LauncherModel.updateItemInDatabase(this, item);
4127 }
4128
4129 if (!mIsSafeModeEnabled && item.restoreStatus == LauncherAppWidgetInfo.RESTORE_COMPLETED) {
4130 final int appWidgetId = item.appWidgetId;
4131 if (DEBUG_WIDGETS) {
4132 Log.d(TAG, "bindAppWidget: id=" + item.appWidgetId + " belongs to component "
4133 + appWidgetInfo.provider);
4134 }
4135
4136 item.hostView = mAppWidgetHost.createView(this, appWidgetId, appWidgetInfo);
4137 } else {
4138 appWidgetInfo = null;
4139 PendingAppWidgetHostView view = new PendingAppWidgetHostView(this, item,
4140 mIsSafeModeEnabled);
4141 view.updateIcon(mIconCache);
4142 item.hostView = view;
4143 item.hostView.updateAppWidget(null);
4144 item.hostView.setOnClickListener(this);
4145 }
4146
4147 item.hostView.setTag(item);
4148 item.onBindAppWidget(this);
4149
4150 workspace.addInScreen(item.hostView, item.container, item.screenId, item.cellX,
4151 item.cellY, item.spanX, item.spanY, false);
4152 if (!item.isCustomWidget()) {
4153 addWidgetToAutoAdvanceIfNeeded(item.hostView, appWidgetInfo);
4154 }
4155
4156 workspace.requestLayout();
4157
4158 if (DEBUG_WIDGETS) {
4159 Log.d(TAG, "bound widget id="+item.appWidgetId+" in "
4160 + (SystemClock.uptimeMillis()-start) + "ms");
4161 }
4162 }
4163
4164 /**
4165 * Restores a pending widget.
4166 *
4167 * @param appWidgetId The app widget id
4168 * @param cellInfo The position on screen where to create the widget.
4169 */
4170 private void completeRestoreAppWidget(final int appWidgetId) {
4171 LauncherAppWidgetHostView view = mWorkspace.getWidgetForAppWidgetId(appWidgetId);
4172 if ((view == null) || !(view instanceof PendingAppWidgetHostView)) {
4173 Log.e(TAG, "Widget update called, when the widget no longer exists.");
4174 return;
4175 }
4176
4177 LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) view.getTag();
4178 info.restoreStatus = LauncherAppWidgetInfo.RESTORE_COMPLETED;
4179
4180 mWorkspace.reinflateWidgetsIfNecessary();
4181 LauncherModel.updateItemInDatabase(this, info);
4182 }
4183
4184 public void onPageBoundSynchronously(int page) {
4185 mSynchronouslyBoundPages.add(page);
4186 }
4187
4188 /**
4189 * Callback saying that there aren't any more items to bind.
4190 *
4191 * Implementation of the method from LauncherModel.Callbacks.
4192 */
4193 public void finishBindingItems() {
4194 Runnable r = new Runnable() {
4195 public void run() {
4196 finishBindingItems();
4197 }
4198 };
4199 if (waitUntilResume(r)) {
4200 return;
4201 }
4202 if (mSavedState != null) {
4203 if (!mWorkspace.hasFocus()) {
4204 mWorkspace.getChildAt(mWorkspace.getCurrentPage()).requestFocus();
4205 }
4206 mSavedState = null;
4207 }
4208
4209 mWorkspace.restoreInstanceStateForRemainingPages();
4210
4211 setWorkspaceLoading(false);
4212 sendLoadingCompleteBroadcastIfNecessary();
4213
4214 // If we received the result of any pending adds while the loader was running (e.g. the
4215 // widget configuration forced an orientation change), process them now.
4216 if (sPendingAddItem != null) {
4217 final long screenId = completeAdd(sPendingAddItem);
4218
4219 // TODO: this moves the user to the page where the pending item was added. Ideally,
4220 // the screen would be guaranteed to exist after bind, and the page would be set through
4221 // the workspace restore process.
4222 mWorkspace.post(new Runnable() {
4223 @Override
4224 public void run() {
4225 mWorkspace.snapToScreenId(screenId);
4226 }
4227 });
4228 sPendingAddItem = null;
4229 }
4230
4231 InstallShortcutReceiver.disableAndFlushInstallQueue(this);
4232
4233 if (mLauncherCallbacks != null) {
4234 mLauncherCallbacks.finishBindingItems(false);
4235 }
4236 }
4237
4238 private void sendLoadingCompleteBroadcastIfNecessary() {
4239 if (!mSharedPrefs.getBoolean(FIRST_LOAD_COMPLETE, false)) {
4240 String permission =
4241 getResources().getString(R.string.receive_first_load_broadcast_permission);
4242 Intent intent = new Intent(ACTION_FIRST_LOAD_COMPLETE);
4243 sendBroadcast(intent, permission);
4244 SharedPreferences.Editor editor = mSharedPrefs.edit();
4245 editor.putBoolean(FIRST_LOAD_COMPLETE, true);
4246 editor.apply();
4247 }
4248 }
4249
4250 public boolean isAllAppsButtonRank(int rank) {
4251 if (mHotseat != null) {
4252 return mHotseat.isAllAppsButtonRank(rank);
4253 }
4254 return false;
4255 }
4256
4257 private boolean canRunNewAppsAnimation() {
4258 long diff = System.currentTimeMillis() - mDragController.getLastGestureUpTime();
4259 return diff > (NEW_APPS_ANIMATION_INACTIVE_TIMEOUT_SECONDS * 1000);
4260 }
4261
4262 private ValueAnimator createNewAppBounceAnimation(View v, int i) {
4263 ValueAnimator bounceAnim = LauncherAnimUtils.ofPropertyValuesHolder(v,
4264 PropertyValuesHolder.ofFloat("alpha", 1f),
4265 PropertyValuesHolder.ofFloat("scaleX", 1f),
4266 PropertyValuesHolder.ofFloat("scaleY", 1f));
4267 bounceAnim.setDuration(InstallShortcutReceiver.NEW_SHORTCUT_BOUNCE_DURATION);
4268 bounceAnim.setStartDelay(i * InstallShortcutReceiver.NEW_SHORTCUT_STAGGER_DELAY);
4269 bounceAnim.setInterpolator(new SmoothPagedView.OvershootInterpolator());
4270 return bounceAnim;
4271 }
4272
4273 public boolean useVerticalBarLayout() {
4274 return mDeviceProfile.isVerticalBarLayout();
4275 }
4276
4277 protected Rect getSearchBarBounds() {
4278 return mDeviceProfile.getSearchBarBounds(Utilities.isRtl(getResources()));
4279 }
4280
4281 public void bindSearchablesChanged() {
4282 if (mSearchDropTargetBar == null) {
4283 return;
4284 }
4285 if (mQsb != null) {
4286 mSearchDropTargetBar.removeView(mQsb);
4287 mQsb = null;
4288 }
4289 mSearchDropTargetBar.setQsbSearchBar(getOrCreateQsbBar());
4290 }
4291
4292 /**
4293 * A runnable that we can dequeue and re-enqueue when all applications are bound (to prevent
4294 * multiple calls to bind the same list.)
4295 */
4296 @Thunk ArrayList<AppInfo> mTmpAppsList;
4297 private Runnable mBindAllApplicationsRunnable = new Runnable() {
4298 public void run() {
4299 bindAllApplications(mTmpAppsList);
4300 mTmpAppsList = null;
4301 }
4302 };
4303
4304 /**
4305 * Add the icons for all apps.
4306 *
4307 * Implementation of the method from LauncherModel.Callbacks.
4308 */
4309 public void bindAllApplications(final ArrayList<AppInfo> apps) {
4310 if (waitUntilResume(mBindAllApplicationsRunnable, true)) {
4311 mTmpAppsList = apps;
4312 return;
4313 }
4314
4315 if (mAppsView != null) {
4316 mAppsView.setApps(apps);
4317 }
4318 if (mLauncherCallbacks != null) {
4319 mLauncherCallbacks.bindAllApplications(apps);
4320 }
4321 }
4322
4323 /**
4324 * A package was updated.
4325 *
4326 * Implementation of the method from LauncherModel.Callbacks.
4327 */
4328 public void bindAppsUpdated(final ArrayList<AppInfo> apps) {
4329 Runnable r = new Runnable() {
4330 public void run() {
4331 bindAppsUpdated(apps);
4332 }
4333 };
4334 if (waitUntilResume(r)) {
4335 return;
4336 }
4337
4338 if (mAppsView != null) {
4339 mAppsView.updateApps(apps);
4340 }
4341 }
4342
4343 @Override
4344 public void bindWidgetsRestored(final ArrayList<LauncherAppWidgetInfo> widgets) {
4345 Runnable r = new Runnable() {
4346 public void run() {
4347 bindWidgetsRestored(widgets);
4348 }
4349 };
4350 if (waitUntilResume(r)) {
4351 return;
4352 }
4353 mWorkspace.widgetsRestored(widgets);
4354 }
4355
4356 /**
4357 * Some shortcuts were updated in the background.
4358 *
4359 * Implementation of the method from LauncherModel.Callbacks.
4360 */
4361 @Override
4362 public void bindShortcutsChanged(final ArrayList<ShortcutInfo> updated,
4363 final ArrayList<ShortcutInfo> removed, final UserHandleCompat user) {
4364 Runnable r = new Runnable() {
4365 public void run() {
4366 bindShortcutsChanged(updated, removed, user);
4367 }
4368 };
4369 if (waitUntilResume(r)) {
4370 return;
4371 }
4372
4373 if (!updated.isEmpty()) {
4374 mWorkspace.updateShortcuts(updated);
4375 }
4376
4377 if (!removed.isEmpty()) {
4378 HashSet<ComponentName> removedComponents = new HashSet<ComponentName>();
4379 for (ShortcutInfo si : removed) {
4380 removedComponents.add(si.getTargetComponent());
4381 }
4382 mWorkspace.removeItemsByComponentName(removedComponents, user);
4383 // Notify the drag controller
4384 mDragController.onAppsRemoved(new ArrayList<String>(), removedComponents);
4385 }
4386 }
4387
4388 /**
4389 * Update the state of a package, typically related to install state.
4390 *
4391 * Implementation of the method from LauncherModel.Callbacks.
4392 */
4393 @Override
4394 public void bindRestoreItemsChange(final HashSet<ItemInfo> updates) {
4395 Runnable r = new Runnable() {
4396 public void run() {
4397 bindRestoreItemsChange(updates);
4398 }
4399 };
4400 if (waitUntilResume(r)) {
4401 return;
4402 }
4403
4404 mWorkspace.updateRestoreItems(updates);
4405 }
4406
4407 /**
4408 * A package was uninstalled. We take both the super set of packageNames
4409 * in addition to specific applications to remove, the reason being that
4410 * this can be called when a package is updated as well. In that scenario,
4411 * we only remove specific components from the workspace, where as
4412 * package-removal should clear all items by package name.
4413 *
4414 * @param reason if non-zero, the icons are not permanently removed, rather marked as disabled.
4415 * Implementation of the method from LauncherModel.Callbacks.
4416 */
4417 @Override
4418 public void bindComponentsRemoved(final ArrayList<String> packageNames,
4419 final ArrayList<AppInfo> appInfos, final UserHandleCompat user, final int reason) {
4420 Runnable r = new Runnable() {
4421 public void run() {
4422 bindComponentsRemoved(packageNames, appInfos, user, reason);
4423 }
4424 };
4425 if (waitUntilResume(r)) {
4426 return;
4427 }
4428
4429 if (reason == 0) {
4430 HashSet<ComponentName> removedComponents = new HashSet<ComponentName>();
4431 for (AppInfo info : appInfos) {
4432 removedComponents.add(info.componentName);
4433 }
4434 if (!packageNames.isEmpty()) {
4435 mWorkspace.removeItemsByPackageName(packageNames, user);
4436 }
4437 if (!removedComponents.isEmpty()) {
4438 mWorkspace.removeItemsByComponentName(removedComponents, user);
4439 }
4440 // Notify the drag controller
4441 mDragController.onAppsRemoved(packageNames, removedComponents);
4442
4443 } else {
4444 mWorkspace.disableShortcutsByPackageName(packageNames, user, reason);
4445 }
4446
4447 // Update AllApps
4448 if (mAppsView != null) {
4449 mAppsView.removeApps(appInfos);
4450 }
4451 }
4452
4453 private Runnable mBindPackagesUpdatedRunnable = new Runnable() {
4454 public void run() {
4455 bindAllPackages(mWidgetsModel);
4456 }
4457 };
4458
4459 @Override
4460 public void bindAllPackages(final WidgetsModel model) {
4461 if (waitUntilResume(mBindPackagesUpdatedRunnable, true)) {
4462 mWidgetsModel = model;
4463 return;
4464 }
4465
4466 if (mWidgetsView != null && model != null) {
4467 mWidgetsView.addWidgets(model);
4468 mWidgetsModel = null;
4469 }
4470 }
4471
4472 private int mapConfigurationOriActivityInfoOri(int configOri) {
4473 final Display d = getWindowManager().getDefaultDisplay();
4474 int naturalOri = Configuration.ORIENTATION_LANDSCAPE;
4475 switch (d.getRotation()) {
4476 case Surface.ROTATION_0:
4477 case Surface.ROTATION_180:
4478 // We are currently in the same basic orientation as the natural orientation
4479 naturalOri = configOri;
4480 break;
4481 case Surface.ROTATION_90:
4482 case Surface.ROTATION_270:
4483 // We are currently in the other basic orientation to the natural orientation
4484 naturalOri = (configOri == Configuration.ORIENTATION_LANDSCAPE) ?
4485 Configuration.ORIENTATION_PORTRAIT : Configuration.ORIENTATION_LANDSCAPE;
4486 break;
4487 }
4488
4489 int[] oriMap = {
4490 ActivityInfo.SCREEN_ORIENTATION_PORTRAIT,
4491 ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE,
4492 ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT,
4493 ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE
4494 };
4495 // Since the map starts at portrait, we need to offset if this device's natural orientation
4496 // is landscape.
4497 int indexOffset = 0;
4498 if (naturalOri == Configuration.ORIENTATION_LANDSCAPE) {
4499 indexOffset = 1;
4500 }
4501 return oriMap[(d.getRotation() + indexOffset) % 4];
4502 }
4503
4504 public void lockScreenOrientation() {
4505 if (Utilities.isRotationEnabled(this)) {
4506 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
4507 setRequestedOrientation(mapConfigurationOriActivityInfoOri(getResources()
4508 .getConfiguration().orientation));
4509 } else {
4510 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LOCKED);
4511 }
4512 }
4513 }
4514 public void unlockScreenOrientation(boolean immediate) {
4515 if (Utilities.isRotationEnabled(this)) {
4516 if (immediate) {
4517 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
4518 } else {
4519 mHandler.postDelayed(new Runnable() {
4520 public void run() {
4521 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
4522 }
4523 }, mRestoreScreenOrientationDelay);
4524 }
4525 }
4526 }
4527
4528 protected boolean isLauncherPreinstalled() {
4529 if (mLauncherCallbacks != null) {
4530 return mLauncherCallbacks.isLauncherPreinstalled();
4531 }
4532 PackageManager pm = getPackageManager();
4533 try {
4534 ApplicationInfo ai = pm.getApplicationInfo(getComponentName().getPackageName(), 0);
4535 if ((ai.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
4536 return true;
4537 } else {
4538 return false;
4539 }
4540 } catch (NameNotFoundException e) {
4541 e.printStackTrace();
4542 return false;
4543 }
4544 }
4545
4546 /**
4547 * This method indicates whether or not we should suggest default wallpaper dimensions
4548 * when our wallpaper cropper was not yet used to set a wallpaper.
4549 */
4550 protected boolean overrideWallpaperDimensions() {
4551 if (mLauncherCallbacks != null) {
4552 return mLauncherCallbacks.overrideWallpaperDimensions();
4553 }
4554 return true;
4555 }
4556
4557 /**
4558 * To be overridden by subclasses to indicate that there is an activity to launch
4559 * before showing the standard launcher experience.
4560 */
4561 protected boolean hasFirstRunActivity() {
4562 if (mLauncherCallbacks != null) {
4563 return mLauncherCallbacks.hasFirstRunActivity();
4564 }
4565 return false;
4566 }
4567
4568 /**
4569 * To be overridden by subclasses to launch any first run activity
4570 */
4571 protected Intent getFirstRunActivity() {
4572 if (mLauncherCallbacks != null) {
4573 return mLauncherCallbacks.getFirstRunActivity();
4574 }
4575 return null;
4576 }
4577
4578 /**
4579 * Returns whether the launcher callbacks overrides search in all apps.
4580 */
4581 @Thunk boolean isAllAppsSearchOverridden() {
4582 if (DISABLE_ALL_APPS_SEARCH_INTEGRATION) {
4583 return false;
4584 }
4585
4586 if (mLauncherCallbacks != null) {
4587 return mLauncherCallbacks.overrideAllAppsSearch();
4588 }
4589 return false;
4590 }
4591
4592 private boolean shouldRunFirstRunActivity() {
4593 return !ActivityManager.isRunningInTestHarness() &&
4594 !mSharedPrefs.getBoolean(FIRST_RUN_ACTIVITY_DISPLAYED, false);
4595 }
4596
4597 protected boolean hasRunFirstRunActivity() {
4598 return mSharedPrefs.getBoolean(FIRST_RUN_ACTIVITY_DISPLAYED, false);
4599 }
4600
4601 public boolean showFirstRunActivity() {
4602 if (shouldRunFirstRunActivity() &&
4603 hasFirstRunActivity()) {
4604 Intent firstRunIntent = getFirstRunActivity();
4605 if (firstRunIntent != null) {
4606 startActivity(firstRunIntent);
4607 markFirstRunActivityShown();
4608 return true;
4609 }
4610 }
4611 return false;
4612 }
4613
4614 private void markFirstRunActivityShown() {
4615 SharedPreferences.Editor editor = mSharedPrefs.edit();
4616 editor.putBoolean(FIRST_RUN_ACTIVITY_DISPLAYED, true);
4617 editor.apply();
4618 }
4619
4620 /**
4621 * To be overridden by subclasses to indicate that there is an in-activity full-screen intro
4622 * screen that must be displayed and dismissed.
4623 */
4624 protected boolean hasDismissableIntroScreen() {
4625 if (mLauncherCallbacks != null) {
4626 return mLauncherCallbacks.hasDismissableIntroScreen();
4627 }
4628 return false;
4629 }
4630
4631 /**
4632 * Full screen intro screen to be shown and dismissed before the launcher can be used.
4633 */
4634 protected View getIntroScreen() {
4635 if (mLauncherCallbacks != null) {
4636 return mLauncherCallbacks.getIntroScreen();
4637 }
4638 return null;
4639 }
4640
4641 /**
4642 * To be overriden by subclasses to indicate whether the in-activity intro screen has been
4643 * dismissed. This method is ignored if #hasDismissableIntroScreen returns false.
4644 */
4645 private boolean shouldShowIntroScreen() {
4646 return hasDismissableIntroScreen() &&
4647 !mSharedPrefs.getBoolean(INTRO_SCREEN_DISMISSED, false);
4648 }
4649
4650 protected void showIntroScreen() {
4651 View introScreen = getIntroScreen();
4652 changeWallpaperVisiblity(false);
4653 if (introScreen != null) {
4654 mDragLayer.showOverlayView(introScreen);
4655 }
4656 if (mLauncherOverlayContainer != null) {
4657 mLauncherOverlayContainer.setVisibility(View.INVISIBLE);
4658 }
4659 }
4660
4661 public void dismissIntroScreen() {
4662 markIntroScreenDismissed();
4663 if (showFirstRunActivity()) {
4664 // We delay hiding the intro view until the first run activity is showing. This
4665 // avoids a blip.
4666 mWorkspace.postDelayed(new Runnable() {
4667 @Override
4668 public void run() {
4669 mDragLayer.dismissOverlayView();
4670 if (mLauncherOverlayContainer != null) {
4671 mLauncherOverlayContainer.setVisibility(View.VISIBLE);
4672 }
4673 showFirstRunClings();
4674 }
4675 }, ACTIVITY_START_DELAY);
4676 } else {
4677 mDragLayer.dismissOverlayView();
4678 if (mLauncherOverlayContainer != null) {
4679 mLauncherOverlayContainer.setVisibility(View.VISIBLE);
4680 }
4681 showFirstRunClings();
4682 }
4683 changeWallpaperVisiblity(true);
4684 }
4685
4686 private void markIntroScreenDismissed() {
4687 SharedPreferences.Editor editor = mSharedPrefs.edit();
4688 editor.putBoolean(INTRO_SCREEN_DISMISSED, true);
4689 editor.apply();
4690 }
4691
4692 @Thunk void showFirstRunClings() {
4693 // The two first run cling paths are mutually exclusive, if the launcher is preinstalled
4694 // on the device, then we always show the first run cling experience (or if there is no
4695 // launcher2). Otherwise, we prompt the user upon started for migration
4696 LauncherClings launcherClings = new LauncherClings(this);
4697 if (launcherClings.shouldShowFirstRunOrMigrationClings()) {
4698 if (mModel.canMigrateFromOldLauncherDb(this)) {
4699 launcherClings.showMigrationCling();
4700 } else {
4701 launcherClings.showLongPressCling(true);
4702 }
4703 }
4704 }
4705
4706 void showWorkspaceSearchAndHotseat() {
4707 if (mWorkspace != null) mWorkspace.setAlpha(1f);
4708 if (mHotseat != null) mHotseat.setAlpha(1f);
4709 if (mPageIndicators != null) mPageIndicators.setAlpha(1f);
4710 if (mSearchDropTargetBar != null) mSearchDropTargetBar.showSearchBar(false);
4711 }
4712
4713 void hideWorkspaceSearchAndHotseat() {
4714 if (mWorkspace != null) mWorkspace.setAlpha(0f);
4715 if (mHotseat != null) mHotseat.setAlpha(0f);
4716 if (mPageIndicators != null) mPageIndicators.setAlpha(0f);
4717 if (mSearchDropTargetBar != null) mSearchDropTargetBar.hideSearchBar(false);
4718 }
4719
4720 public ItemInfo createAppDragInfo(Intent appLaunchIntent) {
4721 // Called from search suggestion, not supported in other profiles.
4722 final UserHandleCompat myUser = UserHandleCompat.myUserHandle();
4723 LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(this);
4724 LauncherActivityInfoCompat activityInfo = launcherApps.resolveActivity(appLaunchIntent,
4725 myUser);
4726 if (activityInfo == null) {
4727 return null;
4728 }
4729 return new AppInfo(this, activityInfo, myUser, mIconCache);
4730 }
4731
4732 public ItemInfo createShortcutDragInfo(Intent shortcutIntent, CharSequence caption,
4733 Bitmap icon) {
4734 // Called from search suggestion, not supported in other profiles.
4735 return createShortcutDragInfo(shortcutIntent, caption, icon,
4736 UserHandleCompat.myUserHandle());
4737 }
4738
4739 public ItemInfo createShortcutDragInfo(Intent shortcutIntent, CharSequence caption,
4740 Bitmap icon, UserHandleCompat user) {
4741 UserManagerCompat userManager = UserManagerCompat.getInstance(this);
4742 CharSequence contentDescription = userManager.getBadgedLabelForUser(caption, user);
4743 return new ShortcutInfo(shortcutIntent, caption, contentDescription, icon, user);
4744 }
4745
4746 protected void moveWorkspaceToDefaultScreen() {
4747 mWorkspace.moveToDefaultScreen(false);
4748 }
4749
4750 public void startDrag(View dragView, ItemInfo dragInfo, DragSource source) {
4751 dragView.setTag(dragInfo);
4752 mWorkspace.onExternalDragStartedWithItem(dragView);
4753 mWorkspace.beginExternalDragShared(dragView, source);
4754 }
4755
4756 @Override
4757 public void onPageSwitch(View newPage, int newPageIndex) {
4758 if (mLauncherCallbacks != null) {
4759 mLauncherCallbacks.onPageSwitch(newPage, newPageIndex);
4760 }
4761 }
4762
4763 /**
4764 * Prints out out state for debugging.
4765 */
4766 public void dumpState() {
4767 Log.d(TAG, "BEGIN launcher3 dump state for launcher " + this);
4768 Log.d(TAG, "mSavedState=" + mSavedState);
4769 Log.d(TAG, "mWorkspaceLoading=" + mWorkspaceLoading);
4770 Log.d(TAG, "mRestoring=" + mRestoring);
4771 Log.d(TAG, "mWaitingForResult=" + mWaitingForResult);
4772 Log.d(TAG, "mSavedInstanceState=" + mSavedInstanceState);
4773 Log.d(TAG, "sFolders.size=" + sFolders.size());
4774 mModel.dumpState();
4775 // TODO(hyunyoungs): add mWidgetsView.dumpState(); or mWidgetsModel.dumpState();
4776
4777 Log.d(TAG, "END launcher3 dump state");
4778 }
4779
4780 @Override
4781 public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
4782 super.dump(prefix, fd, writer, args);
4783 synchronized (sDumpLogs) {
4784 writer.println(" ");
4785 writer.println("Debug logs: ");
4786 for (int i = 0; i < sDumpLogs.size(); i++) {
4787 writer.println(" " + sDumpLogs.get(i));
4788 }
4789 }
4790 if (mLauncherCallbacks != null) {
4791 mLauncherCallbacks.dump(prefix, fd, writer, args);
4792 }
4793 }
4794
4795 public static void dumpDebugLogsToConsole() {
4796 if (DEBUG_DUMP_LOG) {
4797 synchronized (sDumpLogs) {
4798 Log.d(TAG, "");
4799 Log.d(TAG, "*********************");
4800 Log.d(TAG, "Launcher debug logs: ");
4801 for (int i = 0; i < sDumpLogs.size(); i++) {
4802 Log.d(TAG, " " + sDumpLogs.get(i));
4803 }
4804 Log.d(TAG, "*********************");
4805 Log.d(TAG, "");
4806 }
4807 }
4808 }
4809
4810 public static void addDumpLog(String tag, String log, boolean debugLog) {
4811 addDumpLog(tag, log, null, debugLog);
4812 }
4813
4814 public static void addDumpLog(String tag, String log, Exception e, boolean debugLog) {
4815 if (debugLog) {
4816 if (e != null) {
4817 Log.d(tag, log, e);
4818 } else {
4819 Log.d(tag, log);
4820 }
4821 }
4822 if (DEBUG_DUMP_LOG) {
4823 sDateStamp.setTime(System.currentTimeMillis());
4824 synchronized (sDumpLogs) {
4825 sDumpLogs.add(sDateFormat.format(sDateStamp) + ": " + tag + ", " + log
4826 + (e == null ? "" : (", Exception: " + e)));
4827 }
4828 }
4829 }
4830
4831 public static CustomAppWidget getCustomAppWidget(String name) {
4832 return sCustomAppWidgets.get(name);
4833 }
4834
4835 public static HashMap<String, CustomAppWidget> getCustomAppWidgets() {
4836 return sCustomAppWidgets;
4837 }
4838
4839 public void dumpLogsToLocalData() {
4840 if (DEBUG_DUMP_LOG) {
4841 new AsyncTask<Void, Void, Void>() {
4842 public Void doInBackground(Void ... args) {
4843 boolean success = false;
4844 sDateStamp.setTime(sRunStart);
4845 String FILENAME = sDateStamp.getMonth() + "-"
4846 + sDateStamp.getDay() + "_"
4847 + sDateStamp.getHours() + "-"
4848 + sDateStamp.getMinutes() + "_"
4849 + sDateStamp.getSeconds() + ".txt";
4850
4851 FileOutputStream fos = null;
4852 File outFile = null;
4853 try {
4854 outFile = new File(getFilesDir(), FILENAME);
4855 outFile.createNewFile();
4856 fos = new FileOutputStream(outFile);
4857 } catch (Exception e) {
4858 e.printStackTrace();
4859 }
4860 if (fos != null) {
4861 PrintWriter writer = new PrintWriter(fos);
4862
4863 writer.println(" ");
4864 writer.println("Debug logs: ");
4865 synchronized (sDumpLogs) {
4866 for (int i = 0; i < sDumpLogs.size(); i++) {
4867 writer.println(" " + sDumpLogs.get(i));
4868 }
4869 }
4870 writer.close();
4871 }
4872 try {
4873 if (fos != null) {
4874 fos.close();
4875 success = true;
4876 }
4877 } catch (IOException e) {
4878 e.printStackTrace();
4879 }
4880 return null;
4881 }
4882 }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void) null);
4883 }
4884 }
4885 }
4886
4887 interface DebugIntents {
4888 static final String DELETE_DATABASE = "com.android.launcher3.action.DELETE_DATABASE";
4889 static final String MIGRATE_DATABASE = "com.android.launcher3.action.MIGRATE_DATABASE";
4890 } |
1 /*
2 * Copyright (C) 2008 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 package com.android.launcher3;
18
19 import android.animation.Animator;
20 import android.animation.AnimatorListenerAdapter;
21 import android.animation.AnimatorSet;
22 import android.animation.ObjectAnimator;
23 import android.animation.PropertyValuesHolder;
24 import android.animation.ValueAnimator;
25 import android.annotation.SuppressLint;
26 import android.annotation.TargetApi;
27 import android.app.Activity;
28 import android.app.ActivityManager;
29 import android.app.ActivityOptions;
30 import android.app.AlertDialog;
31 import android.app.SearchManager;
32 import android.appwidget.AppWidgetHostView;
33 import android.appwidget.AppWidgetManager;
34 import android.appwidget.AppWidgetProviderInfo;
35 import android.content.ActivityNotFoundException;
36 import android.content.BroadcastReceiver;
37 import android.content.ComponentCallbacks2;
38 import android.content.ComponentName;
39 import android.content.ContentResolver;
40 import android.content.Context;
41 import android.content.DialogInterface;
42 import android.content.Intent;
43 import android.content.IntentFilter;
44 import android.content.IntentSender;
45 import android.content.SharedPreferences;
46 import android.content.pm.ActivityInfo;
47 import android.content.pm.ApplicationInfo;
48 import android.content.pm.PackageManager;
49 import android.content.pm.PackageManager.NameNotFoundException;
50 import android.content.res.Configuration;
51 import android.database.ContentObserver;
52 import android.database.sqlite.SQLiteDatabase;
53 import android.graphics.Bitmap;
54 import android.graphics.Canvas;
55 import android.graphics.Color;
56 import android.graphics.PorterDuff;
57 import android.graphics.Rect;
58 import android.graphics.drawable.ColorDrawable;
59 import android.graphics.drawable.Drawable;
60 import android.net.Uri;
61 import android.os.AsyncTask;
62 import android.os.Build;
63 import android.os.Bundle;
64 import android.os.Environment;
65 import android.os.Handler;
66 import android.os.Message;
67 import android.os.StrictMode;
68 import android.os.SystemClock;
69 import android.text.Selection;
70 import android.text.SpannableStringBuilder;
71 import android.text.TextUtils;
72 import android.text.method.TextKeyListener;
73 import android.util.Log;
74 import android.view.Display;
75 import android.view.Gravity;
76 import android.view.HapticFeedbackConstants;
77 import android.view.KeyEvent;
78 import android.view.LayoutInflater;
79 import android.view.Menu;
80 import android.view.MotionEvent;
81 import android.view.Surface;
82 import android.view.View;
83 import android.view.View.OnClickListener;
84 import android.view.View.OnLongClickListener;
85 import android.view.ViewGroup;
86 import android.view.ViewStub;
87 import android.view.ViewTreeObserver;
88 import android.view.Window;
89 import android.view.WindowManager;
90 import android.view.accessibility.AccessibilityEvent;
91 import android.view.inputmethod.InputMethodManager;
92 import android.widget.Advanceable;
93 import android.widget.FrameLayout;
94 import android.widget.ImageView;
95 import android.widget.TextView;
96 import android.widget.Toast;
97
98 import com.android.launcher3.DropTarget.DragObject;
99 import com.android.launcher3.PagedView.PageSwitchListener;
100 import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
101 import com.android.launcher3.allapps.AllAppsContainerView;
102 import com.android.launcher3.compat.AppWidgetManagerCompat;
103 import com.android.launcher3.compat.LauncherActivityInfoCompat;
104 import com.android.launcher3.compat.LauncherAppsCompat;
105 import com.android.launcher3.compat.UserHandleCompat;
106 import com.android.launcher3.compat.UserManagerCompat;
107 import com.android.launcher3.model.WidgetsModel;
108 import com.android.launcher3.util.LongArrayMap;
109 import com.android.launcher3.util.Thunk;
110 import com.android.launcher3.widget.PendingAddWidgetInfo;
111 import com.android.launcher3.widget.WidgetHostViewLoader;
112 import com.android.launcher3.widget.WidgetsContainerView;
113
114 import java.io.DataInputStream;
115 import java.io.DataOutputStream;
116 import java.io.File;
117 import java.io.FileDescriptor;
118 import java.io.FileNotFoundException;
119 import java.io.FileOutputStream;
120 import java.io.IOException;
121 import java.io.PrintWriter;
122 import java.lang.reflect.InvocationTargetException;
123 import java.lang.reflect.Method;
124 import java.text.DateFormat;
125 import java.util.ArrayList;
126 import java.util.Collection;
127 import java.util.Date;
128 import java.util.HashMap;
129 import java.util.HashSet;
130 import java.util.List;
131 import java.util.concurrent.atomic.AtomicInteger;
132
133 /**
134 * Default launcher application.
135 */
136 public class Launcher extends Activity
137 implements View.OnClickListener, OnLongClickListener, LauncherModel.Callbacks,
138 View.OnTouchListener, PageSwitchListener, LauncherProviderChangeListener,
139 LauncherStateTransitionAnimation.Callbacks {
140 static final String TAG = "Launcher";
141 static final boolean LOGD = false;
142
143 // Temporary flag
144 static final boolean DISABLE_ALL_APPS_SEARCH_INTEGRATION = true;
145
146 static final boolean PROFILE_STARTUP = false;
147 static final boolean DEBUG_WIDGETS = true;
148 static final boolean DEBUG_STRICT_MODE = false;
149 static final boolean DEBUG_RESUME_TIME = false;
150 static final boolean DEBUG_DUMP_LOG = false;
151
152 static final boolean ENABLE_DEBUG_INTENTS = false; // allow DebugIntents to run
153
154 private static final int REQUEST_CREATE_SHORTCUT = 1;
155 private static final int REQUEST_CREATE_APPWIDGET = 5;
156 private static final int REQUEST_PICK_APPWIDGET = 9;
157 private static final int REQUEST_PICK_WALLPAPER = 10;
158
159 private static final int REQUEST_BIND_APPWIDGET = 11;
160 private static final int REQUEST_RECONFIGURE_APPWIDGET = 12;
161
162 private static final int WORKSPACE_BACKGROUND_GRADIENT = 0;
163 private static final int WORKSPACE_BACKGROUND_TRANSPARENT = 1;
164 private static final int WORKSPACE_BACKGROUND_BLACK = 2;
165
166 /**
167 * IntentStarter uses request codes starting with this. This must be greater than all activity
168 * request codes used internally.
169 */
170 protected static final int REQUEST_LAST = 100;
171
172 static final String EXTRA_SHORTCUT_DUPLICATE = "duplicate";
173
174 static final int SCREEN_COUNT = 5;
175
176 // To turn on these properties, type
177 // adb shell setprop log.tag.PROPERTY_NAME [VERBOSE | SUPPRESS]
178 static final String DUMP_STATE_PROPERTY = "launcher_dump_state";
179
180 // The Intent extra that defines whether to ignore the launch animation
181 static final String INTENT_EXTRA_IGNORE_LAUNCH_ANIMATION =
182 "com.android.launcher3.intent.extra.shortcut.INGORE_LAUNCH_ANIMATION";
183
184 // Type: int
185 private static final String RUNTIME_STATE_CURRENT_SCREEN = "launcher.current_screen";
186 // Type: int
187 private static final String RUNTIME_STATE = "launcher.state";
188 // Type: int
189 private static final String RUNTIME_STATE_PENDING_ADD_CONTAINER = "launcher.add_container";
190 // Type: int
191 private static final String RUNTIME_STATE_PENDING_ADD_SCREEN = "launcher.add_screen";
192 // Type: int
193 private static final String RUNTIME_STATE_PENDING_ADD_CELL_X = "launcher.add_cell_x";
194 // Type: int
195 private static final String RUNTIME_STATE_PENDING_ADD_CELL_Y = "launcher.add_cell_y";
196 // Type: int
197 private static final String RUNTIME_STATE_PENDING_ADD_SPAN_X = "launcher.add_span_x";
198 // Type: int
199 private static final String RUNTIME_STATE_PENDING_ADD_SPAN_Y = "launcher.add_span_y";
200 // Type: parcelable
201 private static final String RUNTIME_STATE_PENDING_ADD_WIDGET_INFO = "launcher.add_widget_info";
202 // Type: parcelable
203 private static final String RUNTIME_STATE_PENDING_ADD_WIDGET_ID = "launcher.add_widget_id";
204 // Type: int[]
205 private static final String RUNTIME_STATE_VIEW_IDS = "launcher.view_ids";
206
207 static final String INTRO_SCREEN_DISMISSED = "launcher.intro_screen_dismissed";
208 static final String FIRST_RUN_ACTIVITY_DISPLAYED = "launcher.first_run_activity_displayed";
209
210 static final String FIRST_LOAD_COMPLETE = "launcher.first_load_complete";
211 static final String ACTION_FIRST_LOAD_COMPLETE =
212 "com.android.launcher3.action.FIRST_LOAD_COMPLETE";
213
214 public static final String SHOW_WEIGHT_WATCHER = "debug.show_mem";
215 public static final boolean SHOW_WEIGHT_WATCHER_DEFAULT = false;
216
217 private static final String QSB_WIDGET_ID = "qsb_widget_id";
218 private static final String QSB_WIDGET_PROVIDER = "qsb_widget_provider";
219
220 public static final String USER_HAS_MIGRATED = "launcher.user_migrated_from_old_data";
221
222 /** The different states that Launcher can be in. */
223
224
225 /** The different states that Launcher can be in. */
226 enum State { NONE, WORKSPACE, APPS, APPS_SPRING_LOADED, WIDGETS, WIDGETS_SPRING_LOADED,, };;
227 @Thunk State mState = State.WORKSPACE;
228 @Thunk LauncherStateTransitionAnimation mStateTransitionAnimation;
229
230 private boolean mIsSafeModeEnabled;
231
232 LauncherOverlayCallbacks mLauncherOverlayCallbacks = new LauncherOverlayCallbacksImpl();
233 LauncherOverlay mLauncherOverlay;
234 InsettableFrameLayout mLauncherOverlayContainer;
235
236 static final int APPWIDGET_HOST_ID = 1024;
237 public static final int EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT = 300;
238 private static final int ON_ACTIVITY_RESULT_ANIMATION_DELAY = 500;
239 private static final int ACTIVITY_START_DELAY = 1000;
240
241 private HashMap<Integer, Integer> mItemIdToViewId = new HashMap<Integer, Integer>();
242 private static final AtomicInteger sNextGeneratedId = new AtomicInteger(1);
243
244 // How long to wait before the new-shortcut animation automatically pans the workspace
245 private static int NEW_APPS_PAGE_MOVE_DELAY = 500;
246 private static int NEW_APPS_ANIMATION_INACTIVE_TIMEOUT_SECONDS = 5;
247 @Thunk static int NEW_APPS_ANIMATION_DELAY = 500;
248
249 private final BroadcastReceiver mCloseSystemDialogsReceiver
250 = new CloseSystemDialogsIntentReceiver();
251 private final ContentObserver mWidgetObserver = new AppWidgetResetObserver();
252
253 private LayoutInflater mInflater;
254
255 @Thunk Workspace mWorkspace;
256 private View mLauncherView;
257 private View mPageIndicators;
258 @Thunk DragLayer mDragLayer;
259 private DragController mDragController;
260 private View mWeightWatcher;
261
262 private AppWidgetManagerCompat mAppWidgetManager;
263 private LauncherAppWidgetHost mAppWidgetHost;
264
265 @Thunk ItemInfo mPendingAddInfo = new ItemInfo();
266 private LauncherAppWidgetProviderInfo mPendingAddWidgetInfo;
267 private int mPendingAddWidgetId = -1;
268
269 private int[] mTmpAddItemCellCoordinates = new int[2];
270
271 private Hotseat mHotseat;
272 private ViewGroup mOverviewPanel;
273
274 private View mAllAppsButton;
275
276 private SearchDropTargetBar mSearchDropTargetBar;
277
278 // Main container view for the all apps screen.
279 @Thunk AllAppsContainerView mAppsView;
280
281 // Main container view and the model for the widget tray screen.
282 @Thunk WidgetsContainerView mWidgetsView;
283 @Thunk WidgetsModel mWidgetsModel;
284
285 private boolean mAutoAdvanceRunning = false;
286 private AppWidgetHostView mQsb;
287
288 private Bundle mSavedState;
289 // We set the state in both onCreate and then onNewIntent in some cases, which causes both
290 // scroll issues (because the workspace may not have been measured yet) and extra work.
291 // Instead, just save the state that we need to restore Launcher to, and commit it in onResume.
292 private State mOnResumeState = State.NONE;
293
294 private SpannableStringBuilder mDefaultKeySsb = null;
295
296 @Thunk boolean mWorkspaceLoading = true;
297
298 private boolean mPaused = true;
299 private boolean mRestoring;
300 private boolean mWaitingForResult;
301 private boolean mOnResumeNeedsLoad;
302
303 private ArrayList<Runnable> mBindOnResumeCallbacks = new ArrayList<Runnable>();
304 private ArrayList<Runnable> mOnResumeCallbacks = new ArrayList<Runnable>();
305
306 private Bundle mSavedInstanceState;
307
308 private LauncherModel mModel;
309 private IconCache mIconCache;
310 @Thunk boolean mUserPresent = true;
311 private boolean mVisible = false;
312 private boolean mHasFocus = false;
313 private boolean mAttached = false;
314
315 @Thunk static LocaleConfiguration sLocaleConfiguration = null;
316
317 private static LongArrayMap<FolderInfo> sFolders = new LongArrayMap<>();
318
319 private View.OnTouchListener mHapticFeedbackTouchListener;
320
321 // Related to the auto-advancing of widgets
322 private final int ADVANCE_MSG = 1;
323 private final int mAdvanceInterval = 20000;
324 private final int mAdvanceStagger = 250;
325 private long mAutoAdvanceSentTime;
326 private long mAutoAdvanceTimeLeft = -1;
327 @Thunk HashMap<View, AppWidgetProviderInfo> mWidgetsToAdvance =
328 new HashMap<View, AppWidgetProviderInfo>();
329
330 // Determines how long to wait after a rotation before restoring the screen orientation to
331 // match the sensor state.
332 private final int mRestoreScreenOrientationDelay = 500;
333
334 @Thunk Drawable mWorkspaceBackgroundDrawable;
335
336 private final ArrayList<Integer> mSynchronouslyBoundPages = new ArrayList<Integer>();
337 private static final boolean DISABLE_SYNCHRONOUS_BINDING_CURRENT_PAGE = false;
338
339 static final ArrayList<String> sDumpLogs = new ArrayList<String>();
340 static Date sDateStamp = new Date();
341 static DateFormat sDateFormat =
342 DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT);
343 static long sRunStart = System.currentTimeMillis();
344 static final String CORRUPTION_EMAIL_SENT_KEY = "corruptionEmailSent";
345
346 // We only want to get the SharedPreferences once since it does an FS stat each time we get
347 // it from the context.
348 private SharedPreferences mSharedPrefs;
349
350 // Holds the page that we need to animate to, and the icon views that we need to animate up
351 // when we scroll to that page on resume.
352 @Thunk ImageView mFolderIconImageView;
353 private Bitmap mFolderIconBitmap;
354 private Canvas mFolderIconCanvas;
355 private Rect mRectForFolderAnimation = new Rect();
356
357 private DeviceProfile mDeviceProfile;
358
359 // This is set to the view that launched the activity that navigated the user away from
360 // launcher. Since there is no callback for when the activity has finished launching, enable
361 // the press state and keep this reference to reset the press state when we return to launcher.
362 private BubbleTextView mWaitingForResume;
363
364 protected static HashMap<String, CustomAppWidget> sCustomAppWidgets =
365 new HashMap<String, CustomAppWidget>();
366
367 private static final boolean ENABLE_CUSTOM_WIDGET_TEST = false;
368
369 // TODO: remove this field and call method directly when Launcher3 can depend on M APIs
370 private static Method sClipRevealMethod = null;
371 static {
372 Class<?> activityOptionsClass = ActivityOptions.class;
373 try {
374 sClipRevealMethod = activityOptionsClass.getDeclaredMethod("makeClipRevealAnimation",
375 View.class, int.class, int.class, int.class, int.class);
376 } catch (Exception e) {
377 // Earlier version
378 }
379 }
380 static {
381 if (ENABLE_CUSTOM_WIDGET_TEST) {
382 sCustomAppWidgets.put(DummyWidget.class.getName(), new DummyWidget());
383 }
384 }
385
386 @Thunk Runnable mBuildLayersRunnable = new Runnable() {
387 public void run() {
388 if (mWorkspace != null) {
389 mWorkspace.buildPageHardwareLayers();
390 }
391 }
392 };
393
394 private static PendingAddArguments sPendingAddItem;
395
396 @Thunk static class PendingAddArguments {
397 int requestCode;
398 Intent intent;
399 long container;
400 long screenId;
401 int cellX;
402 int cellY;
403 int appWidgetId;
404 }
405
406 private Stats mStats;
407
408 FocusIndicatorView mFocusHandler;
409
410 @Override
411 protected void onCreate(Bundle savedInstanceState) {
412 if (DEBUG_STRICT_MODE) {
413 StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
414 .detectDiskReads()
415 .detectDiskWrites()
416 .detectNetwork() // or .detectAll() for all detectable problems
417 .penaltyLog()
418 .build());
419 StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
420 .detectLeakedSqlLiteObjects()
421 .detectLeakedClosableObjects()
422 .penaltyLog()
423 .penaltyDeath()
424 .build());
425 }
426
427 if (mLauncherCallbacks != null) {
428 mLauncherCallbacks.preOnCreate();
429 }
430
431 super.onCreate(savedInstanceState);
432
433 LauncherAppState.setApplicationContext(getApplicationContext());
434 LauncherAppState app = LauncherAppState.getInstance();
435 LauncherAppState.getLauncherProvider().setLauncherProviderChangeListener(this);
436
437 // Load configuration-specific DeviceProfile
438 mDeviceProfile = getResources().getConfiguration().orientation
439 == Configuration.ORIENTATION_LANDSCAPE ?
440 app.getInvariantDeviceProfile().landscapeProfile
441 : app.getInvariantDeviceProfile().portraitProfile;
442
443 // TODO: Move this to icon cache.
444 Utilities.setIconSize(mDeviceProfile.iconSizePx);
445
446 // the LauncherApplication should call this, but in case of Instrumentation it might not be prese🔵
447 mSharedPrefs = getSharedPreferences(LauncherAppState.getSharedPreferencesKey(),
448 Context.MODE_PRIVATE);
449 mIsSafeModeEnabled = getPackageManager().isSafeMode();
450 mModel = app.setLauncher(this);
451 mIconCache = app.getIconCache();
452
453 mDragController = new DragController(this);
454 mInflater = getLayoutInflater();
455 mStateTransitionAnimation = new LauncherStateTransitionAnimation(this, this);
456
457 mStats = new Stats(this);
458
459 mAppWidgetManager = AppWidgetManagerCompat.getInstance(this);
460
461 mAppWidgetHost = new LauncherAppWidgetHost(this, APPWIDGET_HOST_ID);
462 mAppWidgetHost.startListening();
463
464 // If we are getting an onCreate, we can actually preempt onResume and unset mPaused here,
465 // this also ensures that any synchronous binding below doesn't re-trigger another
466 // LauncherModel load.
467 mPaused = false;
468
469 if (PROFILE_STARTUP) {
470 android.os.Debug.startMethodTracing(
471 Environment.getExternalStorageDirectory() + "/launcher");
472 }
473
474 checkForLocaleChange();
475 setContentView(R.layout.launcher);
476
477 setupViews();
478 mDeviceProfile.layout(this);
479
480 registerContentObservers();
481
482 lockAllApps();
483
484 mSavedState = savedInstanceState;
485 restoreState(mSavedState);
486
487 if (PROFILE_STARTUP) {
488 android.os.Debug.stopMethodTracing();
489 }
490
491 if (!mRestoring) {
492 if (DISABLE_SYNCHRONOUS_BINDING_CURRENT_PAGE) {
493 // If the user leaves launcher, then we should just load items asynchronously when
494 // they return.
495 mModel.startLoader(PagedView.INVALID_RESTORE_PAGE);
496 } else {
497 // We only load the page synchronously if the user rotates (or triggers a
498 // configuration change) while launcher is in the foreground
499 mModel.startLoader(mWorkspace.getRestorePage());
500 }
501 }
502
503 // For handling default keys
504 mDefaultKeySsb = new SpannableStringBuilder();
505 Selection.setSelection(mDefaultKeySsb, 0);
506
507 IntentFilter filter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
508 registerReceiver(mCloseSystemDialogsReceiver, filter);
509
510 // On large interfaces, we want the screen to auto-rotate based on the current orientation
511 unlockScreenOrientation(true);
512
513 if (mLauncherCallbacks != null) {
514 mLauncherCallbacks.onCreate(savedInstanceState);
515 if (mLauncherCallbacks.hasLauncherOverlay()) {
516 ViewStub stub = (ViewStub) findViewById(R.id.launcher_overlay_stub);
517 mLauncherOverlayContainer = (InsettableFrameLayout) stub.inflate();
518 mLauncherOverlay = mLauncherCallbacks.setLauncherOverlayView(
519 mLauncherOverlayContainer, mLauncherOverlayCallbacks);
520 mWorkspace.setLauncherOverlay(mLauncherOverlay);
521 }
522 }
523
524 if (shouldShowIntroScreen()) {
525 showIntroScreen();
526 } else {
527 showFirstRunActivity();
528 showFirstRunClings();
529 }
530 }
531
532 private LauncherCallbacks mLauncherCallbacks;
533
534 public void onPostCreate(Bundle savedInstanceState) {
535 super.onPostCreate(savedInstanceState);
536 if (mLauncherCallbacks != null) {
537 mLauncherCallbacks.onPostCreate(savedInstanceState);
538 }
539 }
540
541 public boolean setLauncherCallbacks(LauncherCallbacks callbacks) {
542 mLauncherCallbacks = callbacks;
543 mLauncherCallbacks.setLauncherAppsCallback(new Launcher.LauncherAppsCallbacks() {
544 @Override
545 public void onAllAppsBoundsChanged(Rect bounds) {
546 if (LOGD) {
547 Log.d(TAG, "onAllAppsBoundsChanged(Rect): " + bounds);
548 }
549 mAppsView.setFixedBounds(bounds);
550 mWidgetsView.setFixedBounds(bounds);
551 }
552
553 @Override
554 public void dismissAllApps() {
555 if (!DISABLE_ALL_APPS_SEARCH_INTEGRATION) {
556 // Dismiss All Apps if we aren't already paused/invisible
557 if (!mPaused) {
558 showWorkspace(WorkspaceStateTransitionAnimation.SCROLL_TO_CURRENT_PAGE, true,
559 null /* onCompleteRunnable */, false /* notifyLauncherCallbacks */);
560 }
561 }
562 }
563 });
564 return true;
565 }
566
567 @Override
568 public void onLauncherProviderChange() {
569 if (mLauncherCallbacks != null) {
570 mLauncherCallbacks.onLauncherProviderChange();
571 }
572 }
573
574 /** To be overridden by subclasses to hint to Launcher that we have custom content */
575 protected boolean hasCustomContentToLeft() {
576 if (mLauncherCallbacks != null) {
577 return mLauncherCallbacks.hasCustomContentToLeft();
578 }
579 return false;
580 }
581
582 /**
583 * To be overridden by subclasses to populate the custom content container and call
584 * {@link #addToCustomContentPage}. This will only be invoked if
585 * {@link #hasCustomContentToLeft()} is {@code true}.
586 */
587 protected void populateCustomContentContainer() {
588 if (mLauncherCallbacks != null) {
589 mLauncherCallbacks.populateCustomContentContainer();
590 }
591 }
592
593 /**
594 * Invoked by subclasses to signal a change to the {@link #addCustomContentToLeft} value to
595 * ensure the custom content page is added or removed if necessary.
596 */
597 protected void invalidateHasCustomContentToLeft() {
598 if (mWorkspace == null || mWorkspace.getScreenOrder().isEmpty()) {
599 // Not bound yet, wait for bindScreens to be called.
600 return;
601 }
602
603 if (!mWorkspace.hasCustomContent() && hasCustomContentToLeft()) {
604 // Create the custom content page and call the subclass to populate it.
605 mWorkspace.createCustomContentContainer();
606 populateCustomContentContainer();
607 } else if (mWorkspace.hasCustomContent() && !hasCustomContentToLeft()) {
608 mWorkspace.removeCustomContentPage();
609 }
610 }
611
612 @Thunk void checkForLocaleChange() {
613 if (sLocaleConfiguration == null) {
614 new AsyncTask<Void, Void, LocaleConfiguration>() {
615 @Override
616 protected LocaleConfiguration doInBackground(Void... unused) {
617 LocaleConfiguration localeConfiguration = new LocaleConfiguration();
618 readConfiguration(Launcher.this, localeConfiguration);
619 return localeConfiguration;
620 }
621
622 @Override
623 protected void onPostExecute(LocaleConfiguration result) {
624 sLocaleConfiguration = result;
625 checkForLocaleChange(); // recursive, but now with a locale configuration
626 }
627 }.execute();
628 return;
629 }
630
631 final Configuration configuration = getResources().getConfiguration();
632
633 final String previousLocale = sLocaleConfiguration.locale;
634 final String locale = configuration.locale.toString();
635
636 final int previousMcc = sLocaleConfiguration.mcc;
637 final int mcc = configuration.mcc;
638
639 final int previousMnc = sLocaleConfiguration.mnc;
640 final int mnc = configuration.mnc;
641
642 boolean localeChanged = !locale.equals(previousLocale) || mcc != previousMcc || mnc != previousMn🔵
643
644 if (localeChanged) {
645 sLocaleConfiguration.locale = locale;
646 sLocaleConfiguration.mcc = mcc;
647 sLocaleConfiguration.mnc = mnc;
648
649 mIconCache.flush();
650
651 final LocaleConfiguration localeConfiguration = sLocaleConfiguration;
652 new AsyncTask<Void, Void, Void>() {
653 public Void doInBackground(Void ... args) {
654 writeConfiguration(Launcher.this, localeConfiguration);
655 return null;
656 }
657 }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void) null);
658 }
659 }
660
661 @Thunk static class LocaleConfiguration {
662 public String locale;
663 public int mcc = -1;
664 public int mnc = -1;
665 }
666
667 @Thunk static void readConfiguration(Context context, LocaleConfiguration configuration) {
668 DataInputStream in = null;
669 try {
670 in = new DataInputStream(context.openFileInput(LauncherFiles.LAUNCHER_PREFERENCES));
671 configuration.locale = in.readUTF();
672 configuration.mcc = in.readInt();
673 configuration.mnc = in.readInt();
674 } catch (FileNotFoundException e) {
675 // Ignore
676 } catch (IOException e) {
677 // Ignore
678 } finally {
679 if (in != null) {
680 try {
681 in.close();
682 } catch (IOException e) {
683 // Ignore
684 }
685 }
686 }
687 }
688
689 @Thunk static void writeConfiguration(Context context, LocaleConfiguration configuration) {
690 DataOutputStream out = null;
691 try {
692 out = new DataOutputStream(context.openFileOutput(
693 LauncherFiles.LAUNCHER_PREFERENCES, MODE_PRIVATE));
694 out.writeUTF(configuration.locale);
695 out.writeInt(configuration.mcc);
696 out.writeInt(configuration.mnc);
697 out.flush();
698 } catch (FileNotFoundException e) {
699 // Ignore
700 } catch (IOException e) {
701 //noinspection ResultOfMethodCallIgnored
702 context.getFileStreamPath(LauncherFiles.LAUNCHER_PREFERENCES).delete();
703 } finally {
704 if (out != null) {
705 try {
706 out.close();
707 } catch (IOException e) {
708 // Ignore
709 }
710 }
711 }
712 }
713
714 public Stats getStats() {
715 return mStats;
716 }
717
718 public LayoutInflater getInflater() {
719 return mInflater;
720 }
721
722 public boolean isDraggingEnabled() {
723 // We prevent dragging when we are loading the workspace as it is possible to pick up a view
724 // that is subsequently removed from the workspace in startBinding().
725 return !mModel.isLoadingWorkspace();
726 }
727
728 @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
729 public static int generateViewId() {
730 if (Build.VERSION.SDK_INT >= 17) {
731 return View.generateViewId();
732 } else {
733 // View.generateViewId() is not available. The following fallback logic is a copy
734 // of its implementation.
735 for (;;) {
736 final int result = sNextGeneratedId.get();
737 // aapt-generated IDs have the high byte nonzero; clamp to the range under that.
738 int newValue = result + 1;
739 if (newValue > 0x00FFFFFF) newValue = 1; // Roll over to 1, not 0.
740 if (sNextGeneratedId.compareAndSet(result, newValue)) {
741 return result;
742 }
743 }
744 }
745 }
746
747 public int getViewIdForItem(ItemInfo info) {
748 // This cast is safe given the > 2B range for int.
749 int itemId = (int) info.id;
750 if (mItemIdToViewId.containsKey(itemId)) {
751 return mItemIdToViewId.get(itemId);
752 }
753 int viewId = generateViewId();
754 mItemIdToViewId.put(itemId, viewId);
755 return viewId;
756 }
757
758 /**
759 * Returns whether we should delay spring loaded mode -- for shortcuts and widgets that have
760 * a configuration step, this allows the proper animations to run after other transitions.
761 */
762 private long completeAdd(PendingAddArguments args) {
763 long screenId = args.screenId;
764 if (args.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
765 // When the screen id represents an actual screen (as opposed to a rank) we make sure
766 // that the drop page actually exists.
767 screenId = ensurePendingDropLayoutExists(args.screenId);
768 }
769
770 switch (args.requestCode) {
771 case REQUEST_CREATE_SHORTCUT:
772 completeAddShortcut(args.intent, args.container, screenId, args.cellX,
773 args.cellY);
774 break;
775 case REQUEST_CREATE_APPWIDGET:
776 completeAddAppWidget(args.appWidgetId, args.container, screenId, null, null);
777 break;
778 case REQUEST_RECONFIGURE_APPWIDGET:
779 completeRestoreAppWidget(args.appWidgetId);
780 break;
781 }
782 // Before adding this resetAddInfo(), after a shortcut was added to a workspace screen,
783 // if you turned the screen off and then back while in All Apps, Launcher would not
784 // return to the workspace. Clearing mAddInfo.container here fixes this issue
785 resetAddInfo();
786 return screenId;
787 }
788
789 private void handleActivityResult(
790 final int requestCode, final int resultCode, final Intent data) {
791 // Reset the startActivity waiting flag
792 setWaitingForResult(false);
793 final int pendingAddWidgetId = mPendingAddWidgetId;
794 mPendingAddWidgetId = -1;
795
796 Runnable exitSpringLoaded = new Runnable() {
797 @Override
798 public void run() {
799 exitSpringLoadedDragModeDelayed((resultCode != RESULT_CANCELED),
800 EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null);
801 }
802 };
803
804 if (requestCode == REQUEST_BIND_APPWIDGET) {
805 final int appWidgetId = data != null ?
806 data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1) : -1;
807 if (resultCode == RESULT_CANCELED) {
808 completeTwoStageWidgetDrop(RESULT_CANCELED, appWidgetId);
809 mWorkspace.removeExtraEmptyScreenDelayed(true, exitSpringLoaded,
810 ON_ACTIVITY_RESULT_ANIMATION_DELAY, false);
811 } else if (resultCode == RESULT_OK) {
812 addAppWidgetImpl(appWidgetId, mPendingAddInfo, null,
813 mPendingAddWidgetInfo, ON_ACTIVITY_RESULT_ANIMATION_DELAY);
814 }
815 return;
816 } else if (requestCode == REQUEST_PICK_WALLPAPER) {
817 if (resultCode == RESULT_OK && mWorkspace.isInOverviewMode()) {
818 showWorkspace(false);
819 }
820 return;
821 }
822
823 boolean isWidgetDrop = (requestCode == REQUEST_PICK_APPWIDGET ||
824 requestCode == REQUEST_CREATE_APPWIDGET);
825
826 final boolean workspaceLocked = isWorkspaceLocked();
827 // We have special handling for widgets
828 if (isWidgetDrop) {
829 final int appWidgetId;
830 int widgetId = data != null ? data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1)
831 : -1;
832 if (widgetId < 0) {
833 appWidgetId = pendingAddWidgetId;
834 } else {
835 appWidgetId = widgetId;
836 }
837
838 final int result;
839 if (appWidgetId < 0 || resultCode == RESULT_CANCELED) {
840 Log.e(TAG, "Error: appWidgetId (EXTRA_APPWIDGET_ID) was not " +
841 "returned from the widget configuration activity.");
842 result = RESULT_CANCELED;
843 completeTwoStageWidgetDrop(result, appWidgetId);
844 final Runnable onComplete = new Runnable() {
845 @Override
846 public void run() {
847 exitSpringLoadedDragModeDelayed(false, 0, null);
848 }
849 };
850 if (workspaceLocked) {
851 // No need to remove the empty screen if we're mid-binding, as the
852 // the bind will not add the empty screen.
853 mWorkspace.postDelayed(onComplete, ON_ACTIVITY_RESULT_ANIMATION_DELAY);
854 } else {
855 mWorkspace.removeExtraEmptyScreenDelayed(true, onComplete,
856 ON_ACTIVITY_RESULT_ANIMATION_DELAY, false);
857 }
858 } else {
859 if (!workspaceLocked) {
860 if (mPendingAddInfo.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
861 // When the screen id represents an actual screen (as opposed to a rank)
862 // we make sure that the drop page actually exists.
863 mPendingAddInfo.screenId =
864 ensurePendingDropLayoutExists(mPendingAddInfo.screenId);
865 }
866 final CellLayout dropLayout = mWorkspace.getScreenWithId(mPendingAddInfo.screenId);
867
868 dropLayout.setDropPending(true);
869 final Runnable onComplete = new Runnable() {
870 @Override
871 public void run() {
872 completeTwoStageWidgetDrop(resultCode, appWidgetId);
873 dropLayout.setDropPending(false);
874 }
875 };
876 mWorkspace.removeExtraEmptyScreenDelayed(true, onComplete,
877 ON_ACTIVITY_RESULT_ANIMATION_DELAY, false);
878 } else {
879 PendingAddArguments args = preparePendingAddArgs(requestCode, data, appWidgetId,
880 mPendingAddInfo);
881 sPendingAddItem = args;
882 }
883 }
884 return;
885 }
886
887 if (requestCode == REQUEST_RECONFIGURE_APPWIDGET) {
888 if (resultCode == RESULT_OK) {
889 // Update the widget view.
890 PendingAddArguments args = preparePendingAddArgs(requestCode, data,
891 pendingAddWidgetId, mPendingAddInfo);
892 if (workspaceLocked) {
893 sPendingAddItem = args;
894 } else {
895 completeAdd(args);
896 }
897 }
898 // Leave the widget in the pending state if the user canceled the configure.
899 return;
900 }
901
902 // The pattern used here is that a user PICKs a specific application,
903 // which, depending on the target, might need to CREATE the actual target.
904
905 // For example, the user would PICK_SHORTCUT for "Music playlist", and we
906 // launch over to the Music app to actually CREATE_SHORTCUT.
907 if (resultCode == RESULT_OK && mPendingAddInfo.container != ItemInfo.NO_ID) {
908 final PendingAddArguments args = preparePendingAddArgs(requestCode, data, -1,
909 mPendingAddInfo);
910 if (isWorkspaceLocked()) {
911 sPendingAddItem = args;
912 } else {
913 completeAdd(args);
914 mWorkspace.removeExtraEmptyScreenDelayed(true, exitSpringLoaded,
915 ON_ACTIVITY_RESULT_ANIMATION_DELAY, false);
916 }
917 } else if (resultCode == RESULT_CANCELED) {
918 mWorkspace.removeExtraEmptyScreenDelayed(true, exitSpringLoaded,
919 ON_ACTIVITY_RESULT_ANIMATION_DELAY, false);
920 }
921 mDragLayer.clearAnimatedView();
922
923 }
924
925 @Override
926 protected void onActivityResult(
927 final int requestCode, final int resultCode, final Intent data) {
928 handleActivityResult(requestCode, resultCode, data);
929 if (mLauncherCallbacks != null) {
930 mLauncherCallbacks.onActivityResult(requestCode, resultCode, data);
931 }
932 }
933
934 private PendingAddArguments preparePendingAddArgs(int requestCode, Intent data, int
935 appWidgetId, ItemInfo info) {
936 PendingAddArguments args = new PendingAddArguments();
937 args.requestCode = requestCode;
938 args.intent = data;
939 args.container = info.container;
940 args.screenId = info.screenId;
941 args.cellX = info.cellX;
942 args.cellY = info.cellY;
943 args.appWidgetId = appWidgetId;
944 return args;
945 }
946
947 /**
948 * Check to see if a given screen id exists. If not, create it at the end, return the new id.
949 *
950 * @param screenId the screen id to check
951 * @return the new screen, or screenId if it exists
952 */
953 private long ensurePendingDropLayoutExists(long screenId) {
954 CellLayout dropLayout =
955 (CellLayout) mWorkspace.getScreenWithId(screenId);
956 if (dropLayout == null) {
957 // it's possible that the add screen was removed because it was
958 // empty and a re-bind occurred
959 mWorkspace.addExtraEmptyScreen();
960 return mWorkspace.commitExtraEmptyScreen();
961 } else {
962 return screenId;
963 }
964 }
965
966 @Thunk void completeTwoStageWidgetDrop(final int resultCode, final int appWidgetId) {
967 CellLayout cellLayout =
968 (CellLayout) mWorkspace.getScreenWithId(mPendingAddInfo.screenId);
969 Runnable onCompleteRunnable = null;
970 int animationType = 0;
971
972 AppWidgetHostView boundWidget = null;
973 if (resultCode == RESULT_OK) {
974 animationType = Workspace.COMPLETE_TWO_STAGE_WIDGET_DROP_ANIMATION;
975 final AppWidgetHostView layout = mAppWidgetHost.createView(this, appWidgetId,
976 mPendingAddWidgetInfo);
977 boundWidget = layout;
978 onCompleteRunnable = new Runnable() {
979 @Override
980 public void run() {
981 completeAddAppWidget(appWidgetId, mPendingAddInfo.container,
982 mPendingAddInfo.screenId, layout, null);
983 exitSpringLoadedDragModeDelayed((resultCode != RESULT_CANCELED),
984 EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null);
985 }
986 };
987 } else if (resultCode == RESULT_CANCELED) {
988 mAppWidgetHost.deleteAppWidgetId(appWidgetId);
989 animationType = Workspace.CANCEL_TWO_STAGE_WIDGET_DROP_ANIMATION;
990 }
991 if (mDragLayer.getAnimatedView() != null) {
992 mWorkspace.animateWidgetDrop(mPendingAddInfo, cellLayout,
993 (DragView) mDragLayer.getAnimatedView(), onCompleteRunnable,
994 animationType, boundWidget, true);
995 } else if (onCompleteRunnable != null) {
996 // The animated view may be null in the case of a rotation during widget configuration
997 onCompleteRunnable.run();
998 }
999 }
1000
1001 @Override
1002 protected void onStop() {
1003 super.onStop();
1004 FirstFrameAnimatorHelper.setIsVisible(false);
1005
1006 if (mLauncherCallbacks != null) {
1007 mLauncherCallbacks.onStop();
1008 }
1009 }
1010
1011 @Override
1012 protected void onStart() {
1013 super.onStart();
1014 FirstFrameAnimatorHelper.setIsVisible(true);
1015
1016 if (mLauncherCallbacks != null) {
1017 mLauncherCallbacks.onStart();
1018 }
1019 }
1020
1021 @Override
1022 protected void onResume() {
1023 long startTime = 0;
1024 if (DEBUG_RESUME_TIME) {
1025 startTime = System.currentTimeMillis();
1026 Log.v(TAG, "Launcher.onResume()");
1027 }
1028
1029 if (mLauncherCallbacks != null) {
1030 mLauncherCallbacks.preOnResume();
1031 }
1032
1033 super.onResume();
1034
1035 // Restore the previous launcher state
1036 if (mOnResumeState == State.WORKSPACE) {
1037 showWorkspace(false);
1038 } else if (mOnResumeState == State.APPS) {
1039 boolean launchedFromApp = (mWaitingForResume != null);
1040 // Don't update the predicted apps if the user is returning to launcher in the apps
1041 // view after launching an app, as they may be depending on the UI to be static to
1042 // switch to another app, otherwise, if it was
1043 showAppsView(false /* animated */, false /* resetListToTop */,
1044 !launchedFromApp /* updatePredictedApps */);
1045 } else if (mOnResumeState == State.WIDGETS) {
1046 showWidgetsView(false, false);
1047 }
1048 mOnResumeState = State.NONE;
1049
1050 // Restore the apps state if we are in all apps
1051 if (!Launcher.DISABLE_ALL_APPS_SEARCH_INTEGRATION) {
1052 // Otherwise, notify the callbacks if we are in all apps mode
1053 if (mState == State.APPS) {
1054 if (mLauncherCallbacks != null) {
1055 mLauncherCallbacks.onAllAppsShown();
1056 }
1057 }
1058 }
1059
1060 // Background was set to gradient in onPause(), restore to transparent if in all apps.
1061 setWorkspaceBackground(mState == State.WORKSPACE ? WORKSPACE_BACKGROUND_TRANSPARENT
1062 : WORKSPACE_BACKGROUND_GRADIENT);
1063
1064 mPaused = false;
1065 if (mRestoring || mOnResumeNeedsLoad) {
1066 setWorkspaceLoading(true);
1067 mModel.startLoader(PagedView.INVALID_RESTORE_PAGE);
1068 mRestoring = false;
1069 mOnResumeNeedsLoad = false;
1070 }
1071 if (mBindOnResumeCallbacks.size() > 0) {
1072 // We might have postponed some bind calls until onResume (see waitUntilResume) --
1073 // execute them here
1074 long startTimeCallbacks = 0;
1075 if (DEBUG_RESUME_TIME) {
1076 startTimeCallbacks = System.currentTimeMillis();
1077 }
1078
1079 for (int i = 0; i < mBindOnResumeCallbacks.size(); i++) {
1080 mBindOnResumeCallbacks.get(i).run();
1081 }
1082 mBindOnResumeCallbacks.clear();
1083 if (DEBUG_RESUME_TIME) {
1084 Log.d(TAG, "Time spent processing callbacks in onResume: " +
1085 (System.currentTimeMillis() - startTimeCallbacks));
1086 }
1087 }
1088 if (mOnResumeCallbacks.size() > 0) {
1089 for (int i = 0; i < mOnResumeCallbacks.size(); i++) {
1090 mOnResumeCallbacks.get(i).run();
1091 }
1092 mOnResumeCallbacks.clear();
1093 }
1094
1095 // Reset the pressed state of icons that were locked in the press state while activities
1096 // were launching
1097 if (mWaitingForResume != null) {
1098 // Resets the previous workspace icon press state
1099 mWaitingForResume.setStayPressed(false);
1100 }
1101
1102 // It is possible that widgets can receive updates while launcher is not in the foreground.
1103 // Consequently, the widgets will be inflated in the orientation of the foreground activity
1104 // (framework issue). On resuming, we ensure that any widgets are inflated for the current
1105 // orientation.
1106 getWorkspace().reinflateWidgetsIfNecessary();
1107 reinflateQSBIfNecessary();
1108
1109 if (DEBUG_RESUME_TIME) {
1110 Log.d(TAG, "Time spent in onResume: " + (System.currentTimeMillis() - startTime));
1111 }
1112
1113 if (mWorkspace.getCustomContentCallbacks() != null) {
1114 // If we are resuming and the custom content is the current page, we call onShow().
1115 // It is also poassible that onShow will instead be called slightly after first layout
1116 // if PagedView#setRestorePage was set to the custom content page in onCreate().
1117 if (mWorkspace.isOnOrMovingToCustomContent()) {
1118 mWorkspace.getCustomContentCallbacks().onShow(true);
1119 }
1120 }
1121 updateInteraction(Workspace.State.NORMAL, mWorkspace.getState());
1122 mWorkspace.onResume();
1123
1124 if (!isWorkspaceLoading()) {
1125 // Process any items that were added while Launcher was away.
1126 InstallShortcutReceiver.disableAndFlushInstallQueue(this);
1127 }
1128
1129 if (mLauncherCallbacks != null) {
1130 mLauncherCallbacks.onResume();
1131 }
1132 }
1133
1134 @Override
1135 protected void onPause() {
1136 // Ensure that items added to Launcher are queued until Launcher returns
1137 InstallShortcutReceiver.enableInstallQueue();
1138
1139 super.onPause();
1140 mPaused = true;
1141 mDragController.cancelDrag();
1142 mDragController.resetLastGestureUpTime();
1143
1144 // We call onHide() aggressively. The custom content callbacks should be able to
1145 // debounce excess onHide calls.
1146 if (mWorkspace.getCustomContentCallbacks() != null) {
1147 mWorkspace.getCustomContentCallbacks().onHide();
1148 }
1149
1150 if (mLauncherCallbacks != null) {
1151 mLauncherCallbacks.onPause();
1152 }
1153 }
1154
1155 public interface CustomContentCallbacks {
1156 // Custom content is completely shown. {@code fromResume} indicates whether this was caused
1157 // by a onResume or by scrolling otherwise.
1158 public void onShow(boolean fromResume);
1159
1160 // Custom content is completely hidden
1161 public void onHide();
1162
1163 // Custom content scroll progress changed. From 0 (not showing) to 1 (fully showing).
1164 public void onScrollProgressChanged(float progress);
1165
1166 // Indicates whether the user is allowed to scroll away from the custom content.
1167 boolean isScrollingAllowed();
1168 }
1169
1170 public interface LauncherOverlay {
1171
1172 /**
1173 * Touch interaction leading to overscroll has begun
1174 */
1175 public void onScrollInteractionBegin();
1176
1177 /**
1178 * Touch interaction related to overscroll has ended
1179 */
1180 public void onScrollInteractionEnd();
1181
1182 /**
1183 * Scroll progress, between 0 and 100, when the user scrolls beyond the leftmost
1184 * screen (or in the case of RTL, the rightmost screen).
1185 */
1186 public void onScrollChange(int progress, boolean rtl);
1187
1188 /**
1189 * Screen has stopped scrolling
1190 */
1191 public void onScrollSettled();
1192
1193 /**
1194 * This method can be called by the Launcher in order to force the LauncherOverlay
1195 * to exit fully immersive mode.
1196 */
1197 public void forceExitFullImmersion();
1198 }
1199
1200 public interface LauncherAppsCallbacks {
1201 /**
1202 * Updates launcher to the available space that AllApps can take so as not to overlap with
1203 * any other views.
1204 */
1205 public void onAllAppsBoundsChanged(Rect bounds);
1206
1207 /**
1208 * Called to dismiss all apps if it is showing.
1209 */
1210 public void dismissAllApps();
1211 }
1212
1213 public interface LauncherOverlayCallbacks {
1214 /**
1215 * This method indicates whether a call to {@link #enterFullImmersion()} will succeed,
1216 * however it doesn't modify any state within the launcher.
1217 */
1218 public boolean canEnterFullImmersion();
1219
1220 /**
1221 * Should be called to tell Launcher that the LauncherOverlay will take over interaction,
1222 * eg. by occupying the full screen and handling all touch events.
1223 *
1224 * @return true if Launcher allows the LauncherOverlay to become fully immersive. In this
1225 * case, Launcher will modify any necessary state and assumes the overlay is
1226 * handling all interaction. If false, the LauncherOverlay should cancel any
1227 *
1228 */
1229 public boolean enterFullImmersion();
1230
1231 /**
1232 * Must be called when exiting fully immersive mode. Indicates to Launcher that it has
1233 * full control over UI and state.
1234 */
1235 public void exitFullImmersion();
1236 }
1237
1238 class LauncherOverlayCallbacksImpl implements LauncherOverlayCallbacks {
1239
1240 @Override
1241 public boolean canEnterFullImmersion() {
1242 return mState == State.WORKSPACE;
1243 }
1244
1245 @Override
1246 public boolean enterFullImmersion() {
1247 if (mState == State.WORKSPACE) {
1248 // When fully immersed, disregard any touches which fall through.
1249 mDragLayer.setBlockTouch(true);
1250 return true;
1251 }
1252 return false;
1253 }
1254
1255 @Override
1256 public void exitFullImmersion() {
1257 mDragLayer.setBlockTouch(false);
1258 }
1259 }
1260
1261 protected boolean hasSettings() {
1262 if (mLauncherCallbacks != null) {
1263 return mLauncherCallbacks.hasSettings();
1264 }
1265 return false;
1266 }
1267
1268 public void addToCustomContentPage(View customContent,
1269 CustomContentCallbacks callbacks, String description) {
1270 mWorkspace.addToCustomContentPage(customContent, callbacks, description);
1271 }
1272
1273 // The custom content needs to offset its content to account for the QSB
1274 public int getTopOffsetForCustomContent() {
1275 return mWorkspace.getPaddingTop();
1276 }
1277
1278 @Override
1279 public Object onRetainNonConfigurationInstance() {
1280 // Flag the loader to stop early before switching
1281 if (mModel.isCurrentCallbacks(this)) {
1282 mModel.stopLoader();
1283 }
1284 //TODO(hyunyoungs): stop the widgets loader when there is a rotation.
1285
1286 return Boolean.TRUE;
1287 }
1288
1289 // We can't hide the IME if it was forced open. So don't bother
1290 @Override
1291 public void onWindowFocusChanged(boolean hasFocus) {
1292 super.onWindowFocusChanged(hasFocus);
1293 mHasFocus = hasFocus;
1294
1295 if (mLauncherCallbacks != null) {
1296 mLauncherCallbacks.onWindowFocusChanged(hasFocus);
1297 }
1298 }
1299
1300 private boolean acceptFilter() {
1301 final InputMethodManager inputManager = (InputMethodManager)
1302 getSystemService(Context.INPUT_METHOD_SERVICE);
1303 return !inputManager.isFullscreenMode();
1304 }
1305
1306 @Override
1307 public boolean onKeyDown(int keyCode, KeyEvent event) {
1308 final int uniChar = event.getUnicodeChar();
1309 final boolean handled = super.onKeyDown(keyCode, event);
1310 final boolean isKeyNotWhitespace = uniChar > 0 && !Character.isWhitespace(uniChar);
1311 if (!handled && acceptFilter() && isKeyNotWhitespace) {
1312 boolean gotKey = TextKeyListener.getInstance().onKeyDown(mWorkspace, mDefaultKeySsb,
1313 keyCode, event);
1314 if (gotKey && mDefaultKeySsb != null && mDefaultKeySsb.length() > 0) {
1315 // something usable has been typed - start a search
1316 // the typed text will be retrieved and cleared by
1317 // showSearchDialog()
1318 // If there are multiple keystrokes before the search dialog takes focus,
1319 // onSearchRequested() will be called for every keystroke,
1320 // but it is idempotent, so it's fine.
1321 return onSearchRequested();
1322 }
1323 }
1324
1325 // Eat the long press event so the keyboard doesn't come up.
1326 if (keyCode == KeyEvent.KEYCODE_MENU && event.isLongPress()) {
1327 return true;
1328 }
1329
1330 return handled;
1331 }
1332
1333 private String getTypedText() {
1334 return mDefaultKeySsb.toString();
1335 }
1336
1337 private void clearTypedText() {
1338 mDefaultKeySsb.clear();
1339 mDefaultKeySsb.clearSpans();
1340 Selection.setSelection(mDefaultKeySsb, 0);
1341 }
1342
1343 /**
1344 * Given the integer (ordinal) value of a State enum instance, convert it to a variable of type
1345 * State
1346 */
1347 private static State intToState(int stateOrdinal) {
1348 State state = State.WORKSPACE;
1349 final State[] stateValues = State.values();
1350 for (int i = 0; i < stateValues.length; i++) {
1351 if (stateValues[i].ordinal() == stateOrdinal) {
1352 state = stateValues[i];
1353 break;
1354 }
1355 }
1356 return state;
1357 }
1358
1359 /**
1360 * Restores the previous state, if it exists.
1361 *
1362 * @param savedState The previous state.
1363 */
1364 @SuppressWarnings("unchecked")
1365 private void restoreState(Bundle savedState) {
1366 if (savedState == null) {
1367 return;
1368 }
1369
1370 State state = intToState(savedState.getInt(RUNTIME_STATE, State.WORKSPACE.ordinal()));
1371 if (state == State.APPS || state == State.WIDGETS) {
1372 mOnResumeState = state;
1373 }
1374
1375 int currentScreen = savedState.getInt(RUNTIME_STATE_CURRENT_SCREEN,
1376 PagedView.INVALID_RESTORE_PAGE);
1377 if (currentScreen != PagedView.INVALID_RESTORE_PAGE) {
1378 mWorkspace.setRestorePage(currentScreen);
1379 }
1380
1381 final long pendingAddContainer = savedState.getLong(RUNTIME_STATE_PENDING_ADD_CONTAINER, -1);
1382 final long pendingAddScreen = savedState.getLong(RUNTIME_STATE_PENDING_ADD_SCREEN, -1);
1383
1384 if (pendingAddContainer != ItemInfo.NO_ID && pendingAddScreen > -1) {
1385 mPendingAddInfo.container = pendingAddContainer;
1386 mPendingAddInfo.screenId = pendingAddScreen;
1387 mPendingAddInfo.cellX = savedState.getInt(RUNTIME_STATE_PENDING_ADD_CELL_X);
1388 mPendingAddInfo.cellY = savedState.getInt(RUNTIME_STATE_PENDING_ADD_CELL_Y);
1389 mPendingAddInfo.spanX = savedState.getInt(RUNTIME_STATE_PENDING_ADD_SPAN_X);
1390 mPendingAddInfo.spanY = savedState.getInt(RUNTIME_STATE_PENDING_ADD_SPAN_Y);
1391 AppWidgetProviderInfo info = savedState.getParcelable(
1392 RUNTIME_STATE_PENDING_ADD_WIDGET_INFO);
1393 mPendingAddWidgetInfo = info == null ?
1394 null : LauncherAppWidgetProviderInfo.fromProviderInfo(this, info);
1395
1396 mPendingAddWidgetId = savedState.getInt(RUNTIME_STATE_PENDING_ADD_WIDGET_ID);
1397 setWaitingForResult(true);
1398 mRestoring = true;
1399 }
1400
1401 mItemIdToViewId = (HashMap<Integer, Integer>)
1402 savedState.getSerializable(RUNTIME_STATE_VIEW_IDS);
1403 }
1404
1405 /**
1406 * Finds all the views we need and configure them properly.
1407 */
1408 private void setupViews() {
1409 final DragController dragController = mDragController;
1410
1411 mLauncherView = findViewById(R.id.launcher);
1412 mFocusHandler = (FocusIndicatorView) findViewById(R.id.focus_indicator);
1413 mDragLayer = (DragLayer) findViewById(R.id.drag_layer);
1414 mWorkspace = (Workspace) mDragLayer.findViewById(R.id.workspace);
1415 mWorkspace.setPageSwitchListener(this);
1416 mPageIndicators = mDragLayer.findViewById(R.id.page_indicator);
1417
1418 mLauncherView.setSystemUiVisibility(
1419 View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
1420 mWorkspaceBackgroundDrawable = getResources().getDrawable(R.drawable.workspace_bg);
1421
1422 // Setup the drag layer
1423 mDragLayer.setup(this, dragController);
1424
1425 // Setup the hotseat
1426 mHotseat = (Hotseat) findViewById(R.id.hotseat);
1427 if (mHotseat != null) {
1428 mHotseat.setOnLongClickListener(this);
1429 }
1430
1431 mOverviewPanel = (ViewGroup) findViewById(R.id.overview_panel);
1432 View widgetButton = findViewById(R.id.widget_button);
1433 widgetButton.setOnClickListener(new OnClickListener() {
1434 @Override
1435 public void onClick(View arg0) {
1436 if (!mWorkspace.isSwitchingState()) {
1437 onClickAddWidgetButton(arg0);
1438 }
1439 }
1440 });
1441 widgetButton.setOnTouchListener(getHapticFeedbackTouchListener());
1442
1443 View wallpaperButton = findViewById(R.id.wallpaper_button);
1444 wallpaperButton.setOnClickListener(new OnClickListener() {
1445 @Override
1446 public void onClick(View arg0) {
1447 if (!mWorkspace.isSwitchingState()) {
1448 onClickWallpaperPicker(arg0);
1449 }
1450 }
1451 });
1452 wallpaperButton.setOnTouchListener(getHapticFeedbackTouchListener());
1453
1454 View settingsButton = findViewById(R.id.settings_button);
1455 if (hasSettings()) {
1456 settingsButton.setOnClickListener(new OnClickListener() {
1457 @Override
1458 public void onClick(View arg0) {
1459 if (!mWorkspace.isSwitchingState()) {
1460 onClickSettingsButton(arg0);
1461 }
1462 }
1463 });
1464 settingsButton.setOnTouchListener(getHapticFeedbackTouchListener());
1465 } else {
1466 settingsButton.setVisibility(View.GONE);
1467 }
1468
1469 mOverviewPanel.setAlpha(0f);
1470
1471 // Setup the workspace
1472 mWorkspace.setHapticFeedbackEnabled(false);
1473 mWorkspace.setOnLongClickListener(this);
1474 mWorkspace.setup(dragController);
1475 dragController.addDragListener(mWorkspace);
1476
1477 // Get the search/delete bar
1478 mSearchDropTargetBar = (SearchDropTargetBar)
1479 mDragLayer.findViewById(R.id.search_drop_target_bar);
1480
1481 // Setup Apps
1482 mAppsView = (AllAppsContainerView) findViewById(R.id.apps_view);
1483 if (isAllAppsSearchOverridden()) {
1484 mAppsView.hideHeaderBar();
1485 }
1486
1487 // Setup AppsCustomize
1488 mWidgetsView = (WidgetsContainerView) findViewById(R.id.widgets_view);
1489
1490 // Setup the drag controller (drop targets have to be added in reverse order in priority)
1491 dragController.setDragScoller(mWorkspace);
1492 dragController.setScrollView(mDragLayer);
1493 dragController.setMoveTarget(mWorkspace);
1494 dragController.addDropTarget(mWorkspace);
1495 if (mSearchDropTargetBar != null) {
1496 mSearchDropTargetBar.setup(this, dragController);
1497 mSearchDropTargetBar.setQsbSearchBar(getOrCreateQsbBar());
1498 }
1499
1500 if (getResources().getBoolean(R.bool.debug_memory_enabled)) {
1501 Log.v(TAG, "adding WeightWatcher");
1502 mWeightWatcher = new WeightWatcher(this);
1503 mWeightWatcher.setAlpha(0.5f);
1504 ((FrameLayout) mLauncherView).addView(mWeightWatcher,
1505 new FrameLayout.LayoutParams(
1506 FrameLayout.LayoutParams.MATCH_PARENT,
1507 FrameLayout.LayoutParams.WRAP_CONTENT,
1508 Gravity.BOTTOM)
1509 );
1510
1511 boolean show = shouldShowWeightWatcher();
1512 mWeightWatcher.setVisibility(show ? View.VISIBLE : View.GONE);
1513 }
1514 }
1515
1516 /**
1517 * Sets the all apps button. This method is called from {@link Hotseat}.
1518 */
1519 public void setAllAppsButton(View allAppsButton) {
1520 mAllAppsButton = allAppsButton;
1521 }
1522
1523 public View getAllAppsButton() {
1524 return mAllAppsButton;
1525 }
1526
1527 /**
1528 * Creates a view representing a shortcut.
1529 *
1530 * @param info The data structure describing the shortcut.
1531 */
1532 View createShortcut(ShortcutInfo info) {
1533 return createShortcut((ViewGroup) mWorkspace.getChildAt(mWorkspace.getCurrentPage()), info);
1534 }
1535
1536 /**
1537 * Creates a view representing a shortcut inflated from the specified resource.
1538 *
1539 * @param parent The group the shortcut belongs to.
1540 * @param info The data structure describing the shortcut.
1541 *
1542 * @return A View inflated from layoutResId.
1543 */
1544 public View createShortcut(ViewGroup parent, ShortcutInfo info) {
1545 BubbleTextView favorite = (BubbleTextView) mInflater.inflate(R.layout.app_icon,
1546 parent, false);
1547 favorite.applyFromShortcutInfo(info, mIconCache);
1548 favorite.setCompoundDrawablePadding(mDeviceProfile.iconDrawablePaddingPx);
1549 favorite.setOnClickListener(this);
1550 favorite.setOnFocusChangeListener(mFocusHandler);
1551 return favorite;
1552 }
1553
1554 /**
1555 * Add a shortcut to the workspace.
1556 *
1557 * @param data The intent describing the shortcut.
1558 * @param cellInfo The position on screen where to create the shortcut.
1559 */
1560 private void completeAddShortcut(Intent data, long container, long screenId, int cellX,
1561 int cellY) {
1562 int[] cellXY = mTmpAddItemCellCoordinates;
1563 int[] touchXY = mPendingAddInfo.dropPos;
1564 CellLayout layout = getCellLayout(container, screenId);
1565
1566 ShortcutInfo info = InstallShortcutReceiver.fromShortcutIntent(this, data);
1567 if (info == null) {
1568 return;
1569 }
1570 final View view = createShortcut(info);
1571
1572 boolean foundCellSpan = false;
1573 // First we check if we already know the exact location where we want to add this item.
1574 if (cellX >= 0 && cellY >= 0) {
1575 cellXY[0] = cellX;
1576 cellXY[1] = cellY;
1577 foundCellSpan = true;
1578
1579 // If appropriate, either create a folder or add to an existing folder
1580 if (mWorkspace.createUserFolderIfNecessary(view, container, layout, cellXY, 0,
1581 true, null,null)) {
1582 return;
1583 }
1584 DragObject dragObject = new DragObject();
1585 dragObject.dragInfo = info;
1586 if (mWorkspace.addToExistingFolderIfNecessary(view, layout, cellXY, 0, dragObject,
1587 true)) {
1588 return;
1589 }
1590 } else if (touchXY != null) {
1591 // when dragging and dropping, just find the closest free spot
1592 int[] result = layout.findNearestVacantArea(touchXY[0], touchXY[1], 1, 1, cellXY);
1593 foundCellSpan = (result != null);
1594 } else {
1595 foundCellSpan = layout.findCellForSpan(cellXY, 1, 1);
1596 }
1597
1598 if (!foundCellSpan) {
1599 showOutOfSpaceMessage(isHotseatLayout(layout));
1600 return;
1601 }
1602
1603 LauncherModel.addItemToDatabase(this, info, container, screenId, cellXY[0], cellXY[1]);
1604
1605 if (!mRestoring) {
1606 mWorkspace.addInScreen(view, container, screenId, cellXY[0], cellXY[1], 1, 1,
1607 isWorkspaceLocked());
1608 }
1609 }
1610
1611 private int[] getSpanForWidget(ComponentName component, int minWidth, int minHeight) {
1612 Rect padding = AppWidgetHostView.getDefaultPaddingForWidget(this, component, null);
1613 // We want to account for the extra amount of padding that we are adding to the widget
1614 // to ensure that it gets the full amount of space that it has requested
1615 int requiredWidth = minWidth + padding.left + padding.right;
1616 int requiredHeight = minHeight + padding.top + padding.bottom;
1617 return CellLayout.rectToCell(this, requiredWidth, requiredHeight, null);
1618 }
1619
1620 public int[] getSpanForWidget(AppWidgetProviderInfo info) {
1621 return getSpanForWidget(info.provider, info.minWidth, info.minHeight);
1622 }
1623
1624 public int[] getMinSpanForWidget(AppWidgetProviderInfo info) {
1625 return getSpanForWidget(info.provider, info.minResizeWidth, info.minResizeHeight);
1626 }
1627
1628 /**
1629 * Add a widget to the workspace.
1630 *
1631 * @param appWidgetId The app widget id
1632 */
1633 @Thunk void completeAddAppWidget(int appWidgetId, long container, long screenId,
1634 AppWidgetHostView hostView, LauncherAppWidgetProviderInfo appWidgetInfo) {
1635
1636 ItemInfo info = mPendingAddInfo;
1637 if (appWidgetInfo == null) {
1638 appWidgetInfo = LauncherAppWidgetProviderInfo.fromProviderInfo(this,
1639 mAppWidgetManager.getAppWidgetInfo(appWidgetId));
1640 }
1641
1642 if (appWidgetInfo.isCustomWidget) {
1643 appWidgetId = LauncherAppWidgetInfo.CUSTOM_WIDGET_ID;
1644 }
1645
1646 LauncherAppWidgetInfo launcherInfo;
1647 launcherInfo = new LauncherAppWidgetInfo(appWidgetId, appWidgetInfo.provider);
1648 launcherInfo.spanX = info.spanX;
1649 launcherInfo.spanY = info.spanY;
1650 launcherInfo.minSpanX = info.minSpanX;
1651 launcherInfo.minSpanY = info.minSpanY;
1652 launcherInfo.user = mAppWidgetManager.getUser(appWidgetInfo);
1653
1654 LauncherModel.addItemToDatabase(this, launcherInfo,
1655 container, screenId, info.cellX, info.cellY);
1656
1657 if (!mRestoring) {
1658 if (hostView == null) {
1659 // Perform actual inflation because we're live
1660 launcherInfo.hostView = mAppWidgetHost.createView(this, appWidgetId,
1661 appWidgetInfo);
1662 } else {
1663 // The AppWidgetHostView has already been inflated and instantiated
1664 launcherInfo.hostView = hostView;
1665 }
1666 launcherInfo.hostView.setTag(launcherInfo);
1667 launcherInfo.hostView.setVisibility(View.VISIBLE);
1668 launcherInfo.notifyWidgetSizeChanged(this);
1669
1670 mWorkspace.addInScreen(launcherInfo.hostView, container, screenId, info.cellX,
1671 info.cellY, launcherInfo.spanX, launcherInfo.spanY, isWorkspaceLocked());
1672
1673 addWidgetToAutoAdvanceIfNeeded(launcherInfo.hostView, appWidgetInfo);
1674 }
1675 resetAddInfo();
1676 }
1677
1678 private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
1679 @Override
1680 public void onReceive(Context context, Intent intent) {
1681 final String action = intent.getAction();
1682 if (Intent.ACTION_SCREEN_OFF.equals(action)) {
1683 mUserPresent = false;
1684 mDragLayer.clearAllResizeFrames();
1685 updateAutoAdvanceState();
1686
1687 // Reset AllApps to its initial state only if we are not in the middle of
1688 // processing a multi-step drop
1689 if (mAppsView != null && mWidgetsView != null &&
1690 mPendingAddInfo.container == ItemInfo.NO_ID) {
1691 showWorkspace(false);
1692 }
1693 } else if (Intent.ACTION_USER_PRESENT.equals(action)) {
1694 mUserPresent = true;
1695 updateAutoAdvanceState();
1696 } else if (ENABLE_DEBUG_INTENTS && DebugIntents.DELETE_DATABASE.equals(action)) {
1697 mModel.resetLoadedState(false, true);
1698 mModel.startLoader(PagedView.INVALID_RESTORE_PAGE,
1699 LauncherModel.LOADER_FLAG_CLEAR_WORKSPACE);
1700 } else if (ENABLE_DEBUG_INTENTS && DebugIntents.MIGRATE_DATABASE.equals(action)) {
1701 mModel.resetLoadedState(false, true);
1702 mModel.startLoader(PagedView.INVALID_RESTORE_PAGE,
1703 LauncherModel.LOADER_FLAG_CLEAR_WORKSPACE
1704 | LauncherModel.LOADER_FLAG_MIGRATE_SHORTCUTS);
1705 }
1706 }
1707 };
1708
1709 @Override
1710 public void onAttachedToWindow() {
1711 super.onAttachedToWindow();
1712
1713 // Listen for broadcasts related to user-presence
1714 final IntentFilter filter = new IntentFilter();
1715 filter.addAction(Intent.ACTION_SCREEN_OFF);
1716 filter.addAction(Intent.ACTION_USER_PRESENT);
1717 // For handling managed profiles
1718 if (ENABLE_DEBUG_INTENTS) {
1719 filter.addAction(DebugIntents.DELETE_DATABASE);
1720 filter.addAction(DebugIntents.MIGRATE_DATABASE);
1721 }
1722 registerReceiver(mReceiver, filter);
1723 FirstFrameAnimatorHelper.initializeDrawListener(getWindow().getDecorView());
1724 setupTransparentSystemBarsForLmp();
1725 mAttached = true;
1726 mVisible = true;
1727 }
1728
1729 /**
1730 * Sets up transparent navigation and status bars in LMP.
1731 * This method is a no-op for other platform versions.
1732 */
1733 @TargetApi(Build.VERSION_CODES.LOLLIPOP)
1734 private void setupTransparentSystemBarsForLmp() {
1735 if (Utilities.isLmpOrAbove()) {
1736 Window window = getWindow();
1737 window.getAttributes().systemUiVisibility |=
1738 (View.SYSTEM_UI_FLAG_LAYOUT_STABLE
1739 | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
1740 | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
1741 window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS
1742 | WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
1743 window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
1744 window.setStatusBarColor(Color.TRANSPARENT);
1745 window.setNavigationBarColor(Color.TRANSPARENT);
1746 }
1747 }
1748
1749 @Override
1750 public void onDetachedFromWindow() {
1751 super.onDetachedFromWindow();
1752 mVisible = false;
1753
1754 if (mAttached) {
1755 unregisterReceiver(mReceiver);
1756 mAttached = false;
1757 }
1758 updateAutoAdvanceState();
1759 }
1760
1761 public void onWindowVisibilityChanged(int visibility) {
1762 mVisible = visibility == View.VISIBLE;
1763 updateAutoAdvanceState();
1764 // The following code used to be in onResume, but it turns out onResume is called when
1765 // you're in All Apps and click home to go to the workspace. onWindowVisibilityChanged
1766 // is a more appropriate event to handle
1767 if (mVisible) {
1768 if (!mWorkspaceLoading) {
1769 final ViewTreeObserver observer = mWorkspace.getViewTreeObserver();
1770 // We want to let Launcher draw itself at least once before we force it to build
1771 // layers on all the workspace pages, so that transitioning to Launcher from other
1772 // apps is nice and speedy.
1773 observer.addOnDrawListener(new ViewTreeObserver.OnDrawListener() {
1774 private boolean mStarted = false;
1775 public void onDraw() {
1776 if (mStarted) return;
1777 mStarted = true;
1778 // We delay the layer building a bit in order to give
1779 // other message processing a time to run. In particular
1780 // this avoids a delay in hiding the IME if it was
1781 // currently shown, because doing that may involve
1782 // some communication back with the app.
1783 mWorkspace.postDelayed(mBuildLayersRunnable, 500);
1784 final ViewTreeObserver.OnDrawListener listener = this;
1785 mWorkspace.post(new Runnable() {
1786 public void run() {
1787 if (mWorkspace != null &&
1788 mWorkspace.getViewTreeObserver() != null) {
1789 mWorkspace.getViewTreeObserver().
1790 removeOnDrawListener(listener);
1791 }
1792 }
1793 });
1794 return;
1795 }
1796 });
1797 }
1798 clearTypedText();
1799 }
1800 }
1801
1802 @Thunk void sendAdvanceMessage(long delay) {
1803 mHandler.removeMessages(ADVANCE_MSG);
1804 Message msg = mHandler.obtainMessage(ADVANCE_MSG);
1805 mHandler.sendMessageDelayed(msg, delay);
1806 mAutoAdvanceSentTime = System.currentTimeMillis();
1807 }
1808
1809 @Thunk void updateAutoAdvanceState() {
1810 boolean autoAdvanceRunning = mVisible && mUserPresent && !mWidgetsToAdvance.isEmpty();
1811 if (autoAdvanceRunning != mAutoAdvanceRunning) {
1812 mAutoAdvanceRunning = autoAdvanceRunning;
1813 if (autoAdvanceRunning) {
1814 long delay = mAutoAdvanceTimeLeft == -1 ? mAdvanceInterval : mAutoAdvanceTimeLeft;
1815 sendAdvanceMessage(delay);
1816 } else {
1817 if (!mWidgetsToAdvance.isEmpty()) {
1818 mAutoAdvanceTimeLeft = Math.max(0, mAdvanceInterval -
1819 (System.currentTimeMillis() - mAutoAdvanceSentTime));
1820 }
1821 mHandler.removeMessages(ADVANCE_MSG);
1822 mHandler.removeMessages(0); // Remove messages sent using postDelayed()
1823 }
1824 }
1825 }
1826
1827 private final Handler mHandler = new Handler() {
1828 @Override
1829 public void handleMessage(Message msg) {
1830 if (msg.what == ADVANCE_MSG) {
1831 int i = 0;
1832 for (View key: mWidgetsToAdvance.keySet()) {
1833 final View v = key.findViewById(mWidgetsToAdvance.get(key).autoAdvanceViewId);
1834 final int delay = mAdvanceStagger * i;
1835 if (v instanceof Advanceable) {
1836 postDelayed(new Runnable() {
1837 public void run() {
1838 ((Advanceable) v).advance();
1839 }
1840 }, delay);
1841 }
1842 i++;
1843 }
1844 sendAdvanceMessage(mAdvanceInterval);
1845 }
1846 }
1847 };
1848
1849 void addWidgetToAutoAdvanceIfNeeded(View hostView, AppWidgetProviderInfo appWidgetInfo) {
1850 if (appWidgetInfo == null || appWidgetInfo.autoAdvanceViewId == -1) return;
1851 View v = hostView.findViewById(appWidgetInfo.autoAdvanceViewId);
1852 if (v instanceof Advanceable) {
1853 mWidgetsToAdvance.put(hostView, appWidgetInfo);
1854 ((Advanceable) v).fyiWillBeAdvancedByHostKThx();
1855 updateAutoAdvanceState();
1856 }
1857 }
1858
1859 void removeWidgetToAutoAdvance(View hostView) {
1860 if (mWidgetsToAdvance.containsKey(hostView)) {
1861 mWidgetsToAdvance.remove(hostView);
1862 updateAutoAdvanceState();
1863 }
1864 }
1865
1866 public void removeAppWidget(LauncherAppWidgetInfo launcherInfo) {
1867 removeWidgetToAutoAdvance(launcherInfo.hostView);
1868 launcherInfo.hostView = null;
1869 }
1870
1871 public void showOutOfSpaceMessage(boolean isHotseatLayout) {
1872 int strId = (isHotseatLayout ? R.string.hotseat_out_of_space : R.string.out_of_space);
1873 Toast.makeText(this, getString(strId), Toast.LENGTH_SHORT).show();
1874 }
1875
1876 public DragLayer getDragLayer() {
1877 return mDragLayer;
1878 }
1879
1880 public AllAppsContainerView getAppsView() {
1881 return mAppsView;
1882 }
1883
1884 public WidgetsContainerView getWidgetsView() {
1885 return mWidgetsView;
1886 }
1887
1888 public Workspace getWorkspace() {
1889 return mWorkspace;
1890 }
1891
1892 public Hotseat getHotseat() {
1893 return mHotseat;
1894 }
1895
1896 public ViewGroup getOverviewPanel() {
1897 return mOverviewPanel;
1898 }
1899
1900 public SearchDropTargetBar getSearchBar() {
1901 return mSearchDropTargetBar;
1902 }
1903
1904 public LauncherAppWidgetHost getAppWidgetHost() {
1905 return mAppWidgetHost;
1906 }
1907
1908 public LauncherModel getModel() {
1909 return mModel;
1910 }
1911
1912 protected SharedPreferences getSharedPrefs() {
1913 return mSharedPrefs;
1914 }
1915
1916 public DeviceProfile getDeviceProfile() {
1917 return mDeviceProfile;
1918 }
1919
1920 public void closeSystemDialogs() {
1921 getWindow().closeAllPanels();
1922
1923 // Whatever we were doing is hereby canceled.
1924 setWaitingForResult(false);
1925 }
1926
1927 @Override
1928 protected void onNewIntent(Intent intent) {
1929 long startTime = 0;
1930 if (DEBUG_RESUME_TIME) {
1931 startTime = System.currentTimeMillis();
1932 }
1933 super.onNewIntent(intent);
1934
1935 // Close the menu
1936 if (Intent.ACTION_MAIN.equals(intent.getAction())) {
1937 // also will cancel mWaitingForResult.
1938 closeSystemDialogs();
1939
1940 final boolean alreadyOnHome = mHasFocus && ((intent.getFlags() &
1941 Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT)
1942 != Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT);
1943
1944 if (mWorkspace == null) {
1945 // Can be cases where mWorkspace is null, this prevents a NPE
1946 return;
1947 }
1948 Folder openFolder = mWorkspace.getOpenFolder();
1949 // In all these cases, only animate if we're already on home
1950 mWorkspace.exitWidgetResizeMode();
1951
1952 boolean moveToDefaultScreen = mLauncherCallbacks != null ?
1953 mLauncherCallbacks.shouldMoveToDefaultScreenOnHomeIntent() : true;
1954 if (alreadyOnHome && mState == State.WORKSPACE && !mWorkspace.isTouchActive() &&
1955 openFolder == null && moveToDefaultScreen) {
1956 mWorkspace.moveToDefaultScreen(true);
1957 }
1958
1959 closeFolder();
1960 exitSpringLoadedDragMode();
1961
1962 // If we are already on home, then just animate back to the workspace,
1963 // otherwise, just wait until onResume to set the state back to Workspace
1964 if (alreadyOnHome) {
1965 showWorkspace(true);
1966 } else {
1967 mOnResumeState = State.WORKSPACE;
1968 }
1969
1970 final View v = getWindow().peekDecorView();
1971 if (v != null && v.getWindowToken() != null) {
1972 InputMethodManager imm = (InputMethodManager)getSystemService(
1973 INPUT_METHOD_SERVICE);
1974 imm.hideSoftInputFromWindow(v.getWindowToken(), 0);
1975 }
1976
1977 // Reset the apps view
1978 if (!alreadyOnHome && mAppsView != null) {
1979 mAppsView.scrollToTop();
1980 }
1981
1982 // Reset the widgets view
1983 if (!alreadyOnHome && mWidgetsView != null) {
1984 mWidgetsView.scrollToTop();
1985 }
1986
1987 if (mLauncherCallbacks != null) {
1988 mLauncherCallbacks.onHomeIntent();
1989 }
1990 }
1991
1992 if (DEBUG_RESUME_TIME) {
1993 Log.d(TAG, "Time spent in onNewIntent: " + (System.currentTimeMillis() - startTime));
1994 }
1995
1996 if (mLauncherCallbacks != null) {
1997 mLauncherCallbacks.onNewIntent(intent);
1998 }
1999 }
2000
2001 @Override
2002 public void onRestoreInstanceState(Bundle state) {
2003 super.onRestoreInstanceState(state);
2004 for (int page: mSynchronouslyBoundPages) {
2005 mWorkspace.restoreInstanceStateForChild(page);
2006 }
2007 }
2008
2009 @Override
2010 protected void onSaveInstanceState(Bundle outState) {
2011 if (mWorkspace.getChildCount() > 0) {
2012 outState.putInt(RUNTIME_STATE_CURRENT_SCREEN,
2013 mWorkspace.getCurrentPageOffsetFromCustomContent());
2014 }
2015 super.onSaveInstanceState(outState);
2016
2017 outState.putInt(RUNTIME_STATE, mState.ordinal());
2018 // We close any open folder since it will not be re-opened, and we need to make sure
2019 // this state is reflected.
2020 closeFolder();
2021
2022 if (mPendingAddInfo.container != ItemInfo.NO_ID && mPendingAddInfo.screenId > -1 &&
2023 mWaitingForResult) {
2024 outState.putLong(RUNTIME_STATE_PENDING_ADD_CONTAINER, mPendingAddInfo.container);
2025 outState.putLong(RUNTIME_STATE_PENDING_ADD_SCREEN, mPendingAddInfo.screenId);
2026 outState.putInt(RUNTIME_STATE_PENDING_ADD_CELL_X, mPendingAddInfo.cellX);
2027 outState.putInt(RUNTIME_STATE_PENDING_ADD_CELL_Y, mPendingAddInfo.cellY);
2028 outState.putInt(RUNTIME_STATE_PENDING_ADD_SPAN_X, mPendingAddInfo.spanX);
2029 outState.putInt(RUNTIME_STATE_PENDING_ADD_SPAN_Y, mPendingAddInfo.spanY);
2030 outState.putParcelable(RUNTIME_STATE_PENDING_ADD_WIDGET_INFO, mPendingAddWidgetInfo);
2031 outState.putInt(RUNTIME_STATE_PENDING_ADD_WIDGET_ID, mPendingAddWidgetId);
2032 }
2033
2034 // Save the current widgets tray?
2035 // TODO(hyunyoungs)
2036 outState.putSerializable(RUNTIME_STATE_VIEW_IDS, mItemIdToViewId);
2037
2038 if (mLauncherCallbacks != null) {
2039 mLauncherCallbacks.onSaveInstanceState(outState);
2040 }
2041 }
2042
2043 @Override
2044 public void onDestroy() {
2045 super.onDestroy();
2046
2047 // Remove all pending runnables
2048 mHandler.removeMessages(ADVANCE_MSG);
2049 mHandler.removeMessages(0);
2050 mWorkspace.removeCallbacks(mBuildLayersRunnable);
2051
2052 // Stop callbacks from LauncherModel
2053 LauncherAppState app = (LauncherAppState.getInstance());
2054
2055 // It's possible to receive onDestroy after a new Launcher activity has
2056 // been created. In this case, don't interfere with the new Launcher.
2057 if (mModel.isCurrentCallbacks(this)) {
2058 mModel.stopLoader();
2059 app.setLauncher(null);
2060 }
2061
2062 try {
2063 mAppWidgetHost.stopListening();
2064 } catch (NullPointerException ex) {
2065 Log.w(TAG, "problem while stopping AppWidgetHost during Launcher destruction", ex);
2066 }
2067 mAppWidgetHost = null;
2068
2069 mWidgetsToAdvance.clear();
2070
2071 TextKeyListener.getInstance().release();
2072
2073 getContentResolver().unregisterContentObserver(mWidgetObserver);
2074 unregisterReceiver(mCloseSystemDialogsReceiver);
2075
2076 mDragLayer.clearAllResizeFrames();
2077 ((ViewGroup) mWorkspace.getParent()).removeAllViews();
2078 mWorkspace.removeAllWorkspaceScreens();
2079 mWorkspace = null;
2080 mDragController = null;
2081
2082 LauncherAnimUtils.onDestroyActivity();
2083
2084 if (mLauncherCallbacks != null) {
2085 mLauncherCallbacks.onDestroy();
2086 }
2087 }
2088
2089 public DragController getDragController() {
2090 return mDragController;
2091 }
2092
2093 @Override
2094 public void startActivityForResult(Intent intent, int requestCode) {
2095 onStartForResult(requestCode);
2096 super.startActivityForResult(intent, requestCode);
2097 }
2098
2099 @Override
2100 public void startIntentSenderForResult (IntentSender intent, int requestCode,
2101 Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags, Bundle options) {
2102 onStartForResult(requestCode);
2103 try {
2104 super.startIntentSenderForResult(intent, requestCode,
2105 fillInIntent, flagsMask, flagsValues, extraFlags, options);
2106 } catch (IntentSender.SendIntentException e) {
2107 throw new ActivityNotFoundException();
2108 }
2109 }
2110
2111 private void onStartForResult(int requestCode) {
2112 if (requestCode >= 0) {
2113 setWaitingForResult(true);
2114 }
2115 }
2116
2117 /**
2118 * Indicates that we want global search for this activity by setting the globalSearch
2119 * argument for {@link #startSearch} to true.
2120 */
2121 @Override
2122 public void startSearch(String initialQuery, boolean selectInitialQuery,
2123 Bundle appSearchData, boolean globalSearch) {
2124
2125 if (initialQuery == null) {
2126 // Use any text typed in the launcher as the initial query
2127 initialQuery = getTypedText();
2128 }
2129 if (appSearchData == null) {
2130 appSearchData = new Bundle();
2131 appSearchData.putString("source", "launcher-search");
2132 }
2133 Rect sourceBounds = new Rect();
2134 if (mSearchDropTargetBar != null) {
2135 sourceBounds = mSearchDropTargetBar.getSearchBarBounds();
2136 }
2137
2138 boolean clearTextImmediately = startSearch(initialQuery, selectInitialQuery,
2139 appSearchData, sourceBounds);
2140 if (clearTextImmediately) {
2141 clearTypedText();
2142 }
2143
2144 // We need to show the workspace after starting the search
2145 showWorkspace(true);
2146 }
2147
2148 /**
2149 * Start a text search.
2150 *
2151 * @return {@code true} if the search will start immediately, so any further keypresses
2152 * will be handled directly by the search UI. {@code false} if {@link Launcher} should continue
2153 * to buffer keypresses.
2154 */
2155 public boolean startSearch(String initialQuery,
2156 boolean selectInitialQuery, Bundle appSearchData, Rect sourceBounds) {
2157 if (mLauncherCallbacks != null && mLauncherCallbacks.providesSearch()) {
2158 return mLauncherCallbacks.startSearch(initialQuery, selectInitialQuery, appSearchData,
2159 sourceBounds);
2160 }
2161
2162 startGlobalSearch(initialQuery, selectInitialQuery,
2163 appSearchData, sourceBounds);
2164 return false;
2165 }
2166
2167 /**
2168 * Starts the global search activity. This code is a copied from SearchManager
2169 */
2170 private void startGlobalSearch(String initialQuery,
2171 boolean selectInitialQuery, Bundle appSearchData, Rect sourceBounds) {
2172 final SearchManager searchManager =
2173 (SearchManager) getSystemService(Context.SEARCH_SERVICE);
2174 ComponentName globalSearchActivity = searchManager.getGlobalSearchActivity();
2175 if (globalSearchActivity == null) {
2176 Log.w(TAG, "No global search activity found.");
2177 return;
2178 }
2179 Intent intent = new Intent(SearchManager.INTENT_ACTION_GLOBAL_SEARCH);
2180 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
2181 intent.setComponent(globalSearchActivity);
2182 // Make sure that we have a Bundle to put source in
2183 if (appSearchData == null) {
2184 appSearchData = new Bundle();
2185 } else {
2186 appSearchData = new Bundle(appSearchData);
2187 }
2188 // Set source to package name of app that starts global search if not set already.
2189 if (!appSearchData.containsKey("source")) {
2190 appSearchData.putString("source", getPackageName());
2191 }
2192 intent.putExtra(SearchManager.APP_DATA, appSearchData);
2193 if (!TextUtils.isEmpty(initialQuery)) {
2194 intent.putExtra(SearchManager.QUERY, initialQuery);
2195 }
2196 if (selectInitialQuery) {
2197 intent.putExtra(SearchManager.EXTRA_SELECT_QUERY, selectInitialQuery);
2198 }
2199 intent.setSourceBounds(sourceBounds);
2200 try {
2201 startActivity(intent);
2202 } catch (ActivityNotFoundException ex) {
2203 Log.e(TAG, "Global search activity not found: " + globalSearchActivity);
2204 }
2205 }
2206
2207 public boolean isOnCustomContent() {
2208 return mWorkspace.isOnOrMovingToCustomContent();
2209 }
2210
2211 @Override
2212 public boolean onPrepareOptionsMenu(Menu menu) {
2213 super.onPrepareOptionsMenu(menu);
2214 if (!isOnCustomContent()) {
2215 // Close any open folders
2216 closeFolder();
2217 // Stop resizing any widgets
2218 mWorkspace.exitWidgetResizeMode();
2219 if (!mWorkspace.isInOverviewMode()) {
2220 // Show the overview mode
2221 showOverviewMode(true);
2222 } else {
2223 showWorkspace(true);
2224 }
2225 }
2226 if (mLauncherCallbacks != null) {
2227 return mLauncherCallbacks.onPrepareOptionsMenu(menu);
2228 }
2229
2230 return false;
2231 }
2232
2233 @Override
2234 public boolean onSearchRequested() {
2235 startSearch(null, false, null, true);
2236 // Use a custom animation for launching search
2237 return true;
2238 }
2239
2240 public boolean isWorkspaceLocked() {
2241 return mWorkspaceLoading || mWaitingForResult;
2242 }
2243
2244 public boolean isWorkspaceLoading() {
2245 return mWorkspaceLoading;
2246 }
2247
2248 private void setWorkspaceLoading(boolean value) {
2249 boolean isLocked = isWorkspaceLocked();
2250 mWorkspaceLoading = value;
2251 if (isLocked != isWorkspaceLocked()) {
2252 onWorkspaceLockedChanged();
2253 }
2254 }
2255
2256 private void setWaitingForResult(boolean value) {
2257 boolean isLocked = isWorkspaceLocked();
2258 mWaitingForResult = value;
2259 if (isLocked != isWorkspaceLocked()) {
2260 onWorkspaceLockedChanged();
2261 }
2262 }
2263
2264 protected void onWorkspaceLockedChanged() {
2265 if (mLauncherCallbacks != null) {
2266 mLauncherCallbacks.onWorkspaceLockedChanged();
2267 }
2268 }
2269
2270 private void resetAddInfo() {
2271 mPendingAddInfo.container = ItemInfo.NO_ID;
2272 mPendingAddInfo.screenId = -1;
2273 mPendingAddInfo.cellX = mPendingAddInfo.cellY = -1;
2274 mPendingAddInfo.spanX = mPendingAddInfo.spanY = -1;
2275 mPendingAddInfo.minSpanX = mPendingAddInfo.minSpanY = -1;
2276 mPendingAddInfo.dropPos = null;
2277 }
2278
2279 void addAppWidgetImpl(final int appWidgetId, final ItemInfo info, final
2280 AppWidgetHostView boundWidget, final LauncherAppWidgetProviderInfo appWidgetInfo) {
2281 addAppWidgetImpl(appWidgetId, info, boundWidget, appWidgetInfo, 0);
2282 }
2283
2284 void addAppWidgetImpl(final int appWidgetId, final ItemInfo info,
2285 final AppWidgetHostView boundWidget, final LauncherAppWidgetProviderInfo appWidgetInfo,
2286 int delay) {
2287 if (appWidgetInfo.configure != null) {
2288 mPendingAddWidgetInfo = appWidgetInfo;
2289 mPendingAddWidgetId = appWidgetId;
2290
2291 // Launch over to configure widget, if needed
2292 mAppWidgetManager.startConfigActivity(appWidgetInfo, appWidgetId, this,
2293 mAppWidgetHost, REQUEST_CREATE_APPWIDGET);
2294
2295 } else {
2296 // Otherwise just add it
2297 Runnable onComplete = new Runnable() {
2298 @Override
2299 public void run() {
2300 // Exit spring loaded mode if necessary after adding the widget
2301 exitSpringLoadedDragModeDelayed(true, EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT,
2302 null);
2303 }
2304 };
2305 completeAddAppWidget(appWidgetId, info.container, info.screenId, boundWidget,
2306 appWidgetInfo);
2307 mWorkspace.removeExtraEmptyScreenDelayed(true, onComplete, delay, false);
2308 }
2309 }
2310
2311 protected void moveToCustomContentScreen(boolean animate) {
2312 // Close any folders that may be open.
2313 closeFolder();
2314 mWorkspace.moveToCustomContentScreen(animate);
2315 }
2316
2317 public void addPendingItem(PendingAddItemInfo info, long container, long screenId,
2318 int[] cell, int spanX, int spanY) {
2319 switch (info.itemType) {
2320 case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET:
2321 case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
2322 int span[] = new int[2];
2323 span[0] = spanX;
2324 span[1] = spanY;
2325 addAppWidgetFromDrop((PendingAddWidgetInfo) info,
2326 container, screenId, cell, span);
2327 break;
2328 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
2329 processShortcutFromDrop(info.componentName, container, screenId, cell);
2330 break;
2331 default:
2332 throw new IllegalStateException("Unknown item type: " + info.itemType);
2333 }
2334 }
2335
2336 /**
2337 * Process a shortcut drop.
2338 *
2339 * @param componentName The name of the component
2340 * @param screenId The ID of the screen where it should be added
2341 * @param cell The cell it should be added to, optional
2342 */
2343 private void processShortcutFromDrop(ComponentName componentName, long container, long screenId,
2344 int[] cell) {
2345 resetAddInfo();
2346 mPendingAddInfo.container = container;
2347 mPendingAddInfo.screenId = screenId;
2348 mPendingAddInfo.dropPos = null;
2349
2350 if (cell != null) {
2351 mPendingAddInfo.cellX = cell[0];
2352 mPendingAddInfo.cellY = cell[1];
2353 }
2354
2355 Intent createShortcutIntent = new Intent(Intent.ACTION_CREATE_SHORTCUT);
2356 createShortcutIntent.setComponent(componentName);
2357 processShortcut(createShortcutIntent);
2358 }
2359
2360 /**
2361 * Process a widget drop.
2362 *
2363 * @param info The PendingAppWidgetInfo of the widget being added.
2364 * @param screenId The ID of the screen where it should be added
2365 * @param cell The cell it should be added to, optional
2366 */
2367 private void addAppWidgetFromDrop(PendingAddWidgetInfo info, long container, long screenId,
2368 int[] cell, int[] span) {
2369 resetAddInfo();
2370 mPendingAddInfo.container = info.container = container;
2371 mPendingAddInfo.screenId = info.screenId = screenId;
2372 mPendingAddInfo.dropPos = null;
2373 mPendingAddInfo.minSpanX = info.minSpanX;
2374 mPendingAddInfo.minSpanY = info.minSpanY;
2375
2376 if (cell != null) {
2377 mPendingAddInfo.cellX = cell[0];
2378 mPendingAddInfo.cellY = cell[1];
2379 }
2380 if (span != null) {
2381 mPendingAddInfo.spanX = span[0];
2382 mPendingAddInfo.spanY = span[1];
2383 }
2384
2385 AppWidgetHostView hostView = info.boundWidget;
2386 int appWidgetId;
2387 if (hostView != null) {
2388 appWidgetId = hostView.getAppWidgetId();
2389 addAppWidgetImpl(appWidgetId, info, hostView, info.info);
2390 } else {
2391 // In this case, we either need to start an activity to get permission to bind
2392 // the widget, or we need to start an activity to configure the widget, or both.
2393 appWidgetId = getAppWidgetHost().allocateAppWidgetId();
2394 Bundle options = info.bindOptions;
2395
2396 boolean success = mAppWidgetManager.bindAppWidgetIdIfAllowed(
2397 appWidgetId, info.info, options);
2398 if (success) {
2399 addAppWidgetImpl(appWidgetId, info, null, info.info);
2400 } else {
2401 mPendingAddWidgetInfo = info.info;
2402 Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_BIND);
2403 intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
2404 intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER, info.componentName);
2405 mAppWidgetManager.getUser(mPendingAddWidgetInfo)
2406 .addToIntent(intent, AppWidgetManager.EXTRA_APPWIDGET_PROVIDER_PROFILE);
2407 // TODO: we need to make sure that this accounts for the options bundle.
2408 // intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_OPTIONS, options);
2409 startActivityForResult(intent, REQUEST_BIND_APPWIDGET);
2410 }
2411 }
2412 }
2413
2414 void processShortcut(Intent intent) {
2415 Utilities.startActivityForResultSafely(this, intent, REQUEST_CREATE_SHORTCUT);
2416 }
2417
2418 void processWallpaper(Intent intent) {
2419 startActivityForResult(intent, REQUEST_PICK_WALLPAPER);
2420 }
2421
2422 FolderIcon addFolder(CellLayout layout, long container, final long screenId, int cellX,
2423 int cellY) {
2424 final FolderInfo folderInfo = new FolderInfo();
2425 folderInfo.title = getText(R.string.folder_name);
2426
2427 // Update the model
2428 LauncherModel.addItemToDatabase(Launcher.this, folderInfo, container, screenId,
2429 cellX, cellY);
2430 sFolders.put(folderInfo.id, folderInfo);
2431
2432 // Create the view
2433 FolderIcon newFolder =
2434 FolderIcon.fromXml(R.layout.folder_icon, this, layout, folderInfo, mIconCache);
2435 mWorkspace.addInScreen(newFolder, container, screenId, cellX, cellY, 1, 1,
2436 isWorkspaceLocked());
2437 // Force measure the new folder icon
2438 CellLayout parent = mWorkspace.getParentCellLayoutForView(newFolder);
2439 parent.getShortcutsAndWidgets().measureChild(newFolder);
2440 return newFolder;
2441 }
2442
2443 void removeFolder(FolderInfo folder) {
2444 sFolders.remove(folder.id);
2445 }
2446
2447 /**
2448 * Registers various content observers. The current implementation registers
2449 * only a favorites observer to keep track of the favorites applications.
2450 */
2451 private void registerContentObservers() {
2452 ContentResolver resolver = getContentResolver();
2453 resolver.registerContentObserver(LauncherProvider.CONTENT_APPWIDGET_RESET_URI,
2454 true, mWidgetObserver);
2455 }
2456
2457 @Override
2458 public boolean dispatchKeyEvent(KeyEvent event) {
2459 if (event.getAction() == KeyEvent.ACTION_DOWN) {
2460 switch (event.getKeyCode()) {
2461 case KeyEvent.KEYCODE_HOME:
2462 return true;
2463 case KeyEvent.KEYCODE_VOLUME_DOWN:
2464 if (Utilities.isPropertyEnabled(DUMP_STATE_PROPERTY)) {
2465 dumpState();
2466 return true;
2467 }
2468 break;
2469 }
2470 } else if (event.getAction() == KeyEvent.ACTION_UP) {
2471 switch (event.getKeyCode()) {
2472 case KeyEvent.KEYCODE_HOME:
2473 return true;
2474 }
2475 }
2476
2477 return super.dispatchKeyEvent(event);
2478 }
2479
2480 @Override
2481 public void onBackPressed() {
2482 if (mLauncherCallbacks != null && mLauncherCallbacks.handleBackPressed()) {
2483 return;
2484 }
2485
2486 LauncherAccessibilityDelegate delegate =
2487 LauncherAppState.getInstance().getAccessibilityDelegate();
2488 if (delegate != null && delegate.onBackPressed()) {
2489 return;
2490 }
2491
2492 if (isAppsViewVisible()) {
2493 showWorkspace(true);
2494 } else if (isWidgetsViewVisible()) {
2495 showOverviewMode(true);
2496 } else if (mWorkspace.isInOverviewMode()) {
2497 showWorkspace(true);
2498 } else if (mWorkspace.getOpenFolder() != null) {
2499 Folder openFolder = mWorkspace.getOpenFolder();
2500 if (openFolder.isEditingName()) {
2501 openFolder.dismissEditingName();
2502 } else {
2503 closeFolder();
2504 }
2505 } else {
2506 mWorkspace.exitWidgetResizeMode();
2507
2508 // Back button is a no-op here, but give at least some feedback for the button press
2509 mWorkspace.showOutlinesTemporarily();
2510 }
2511 }
2512
2513 /**
2514 * Re-listen when widgets are reset.
2515 */
2516 @Thunk void onAppWidgetReset() {
2517 if (mAppWidgetHost != null) {
2518 mAppWidgetHost.startListening();
2519 }
2520 }
2521
2522 /**
2523 * Launches the intent referred by the clicked shortcut.
2524 *
2525 * @param v The view representing the clicked shortcut.
2526 */
2527 public void onClick(View v) {
2528 // Make sure that rogue clicks don't get through while allapps is launching, or after the
2529 // view has detached (it's possible for this to happen if the view is removed mid touch).
2530 if (v.getWindowToken() == null) {
2531 return;
2532 }
2533
2534 if (!mWorkspace.isFinishedSwitchingState()) {
2535 return;
2536 }
2537
2538 if (v instanceof Workspace) {
2539 if (mWorkspace.isInOverviewMode()) {
2540 showWorkspace(true);
2541 }
2542 return;
2543 }
2544
2545 if (v instanceof CellLayout) {
2546 if (mWorkspace.isInOverviewMode()) {
2547 showWorkspace(mWorkspace.indexOfChild(v), true);
2548 }
2549 }
2550
2551 Object tag = v.getTag();
2552 if (tag instanceof ShortcutInfo) {
2553 onClickAppShortcut(v);
2554 } else if (tag instanceof FolderInfo) {
2555 if (v instanceof FolderIcon) {
2556 onClickFolderIcon(v);
2557 }
2558 } else if (v == mAllAppsButton) {
2559 onClickAllAppsButton(v);
2560 } else if (tag instanceof AppInfo) {
2561 startAppShortcutOrInfoActivity(v);
2562 } else if (tag instanceof LauncherAppWidgetInfo) {
2563 if (v instanceof PendingAppWidgetHostView) {
2564 onClickPendingWidget((PendingAppWidgetHostView) v);
2565 }
2566 }
2567 }
2568
2569 public void onClickPagedViewIcon(View v) {
2570 startAppShortcutOrInfoActivity(v);
2571 if (mLauncherCallbacks != null) {
2572 mLauncherCallbacks.onClickPagedViewIcon(v);
2573 }
2574 }
2575
2576 @SuppressLint("ClickableViewAccessibility")
2577 public boolean onTouch(View v, MotionEvent event) {
2578 return false;
2579 }
2580
2581 /**
2582 * Event handler for the app widget view which has not fully restored.
2583 */
2584 public void onClickPendingWidget(final PendingAppWidgetHostView v) {
2585 if (mIsSafeModeEnabled) {
2586 Toast.makeText(this, R.string.safemode_widget_error, Toast.LENGTH_SHORT).show();
2587 return;
2588 }
2589
2590 final LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) v.getTag();
2591 if (v.isReadyForClickSetup()) {
2592 int widgetId = info.appWidgetId;
2593 AppWidgetProviderInfo appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(widgetId);
2594 if (appWidgetInfo != null) {
2595 mPendingAddWidgetInfo = LauncherAppWidgetProviderInfo.fromProviderInfo(
2596 this, appWidgetInfo);
2597 mPendingAddInfo.copyFrom(info);
2598 mPendingAddWidgetId = widgetId;
2599
2600 AppWidgetManagerCompat.getInstance(this).startConfigActivity(appWidgetInfo,
2601 info.appWidgetId, this, mAppWidgetHost, REQUEST_RECONFIGURE_APPWIDGET);
2602 }
2603 } else if (info.installProgress < 0) {
2604 // The install has not been queued
2605 final String packageName = info.providerName.getPackageName();
2606 showBrokenAppInstallDialog(packageName,
2607 new DialogInterface.OnClickListener() {
2608 public void onClick(DialogInterface dialog, int id) {
2609 startActivitySafely(v, LauncherModel.getMarketIntent(packageName), info);
2610 }
2611 });
2612 } else {
2613 // Download has started.
2614 final String packageName = info.providerName.getPackageName();
2615 startActivitySafely(v, LauncherModel.getMarketIntent(packageName), info);
2616 }
2617 }
2618
2619 /**
2620 * Event handler for the "grid" button that appears on the home screen, which
2621 * enters all apps mode.
2622 *
2623 * @param v The view that was clicked.
2624 */
2625 protected void onClickAllAppsButton(View v) {
2626 if (LOGD) Log.d(TAG, "onClickAllAppsButton");
2627 if (isAppsViewVisible()) {
2628 showWorkspace(true);
2629 } else {
2630 // Try and refresh the set of predicted apps before we enter launcher
2631 showAppsView(true /* animated */, false /* resetListToTop */,
2632 true /* updatePredictedApps */);
2633 }
2634 }
2635
2636 private void showBrokenAppInstallDialog(final String packageName,
2637 DialogInterface.OnClickListener onSearchClickListener) {
2638 new AlertDialog.Builder(this)
2639 .setTitle(R.string.abandoned_promises_title)
2640 .setMessage(R.string.abandoned_promise_explanation)
2641 .setPositiveButton(R.string.abandoned_search, onSearchClickListener)
2642 .setNeutralButton(R.string.abandoned_clean_this,
2643 new DialogInterface.OnClickListener() {
2644 public void onClick(DialogInterface dialog, int id) {
2645 final UserHandleCompat user = UserHandleCompat.myUserHandle();
2646 mWorkspace.removeAbandonedPromise(packageName, user);
2647 }
2648 })
2649 .create().show();
2650 return;
2651 }
2652
2653 /**
2654 * Event handler for an app shortcut click.
2655 *
2656 * @param v The view that was clicked. Must be a tagged with a {@link ShortcutInfo}.
2657 */
2658 protected void onClickAppShortcut(final View v) {
2659 if (LOGD) Log.d(TAG, "onClickAppShortcut");
2660 Object tag = v.getTag();
2661 if (!(tag instanceof ShortcutInfo)) {
2662 throw new IllegalArgumentException("Input must be a Shortcut");
2663 }
2664
2665 // Open shortcut
2666 final ShortcutInfo shortcut = (ShortcutInfo) tag;
2667
2668 if (shortcut.isDisabled != 0) {
2669 int error = R.string.activity_not_available;
2670 if ((shortcut.isDisabled & ShortcutInfo.FLAG_DISABLED_SAFEMODE) != 0) {
2671 error = R.string.safemode_shortcut_error;
2672 }
2673 Toast.makeText(this, error, Toast.LENGTH_SHORT).show();
2674 return;
2675 }
2676
2677 final Intent intent = shortcut.intent;
2678
2679 // Check for special shortcuts
2680 if (intent.getComponent() != null) {
2681 final String shortcutClass = intent.getComponent().getClassName();
2682
2683 if (shortcutClass.equals(MemoryDumpActivity.class.getName())) {
2684 MemoryDumpActivity.startDump(this);
2685 return;
2686 } else if (shortcutClass.equals(ToggleWeightWatcher.class.getName())) {
2687 toggleShowWeightWatcher();
2688 return;
2689 }
2690 }
2691
2692 // Check for abandoned promise
2693 if ((v instanceof BubbleTextView)
2694 && shortcut.isPromise()
2695 && !shortcut.hasStatusFlag(ShortcutInfo.FLAG_INSTALL_SESSION_ACTIVE)) {
2696 showBrokenAppInstallDialog(
2697 shortcut.getTargetComponent().getPackageName(),
2698 new DialogInterface.OnClickListener() {
2699 public void onClick(DialogInterface dialog, int id) {
2700 startAppShortcutOrInfoActivity(v);
2701 }
2702 });
2703 return;
2704 }
2705
2706 // Start activities
2707 startAppShortcutOrInfoActivity(v);
2708
2709 if (mLauncherCallbacks != null) {
2710 mLauncherCallbacks.onClickAppShortcut(v);
2711 }
2712 }
2713
2714 @Thunk void startAppShortcutOrInfoActivity(View v) {
2715 Object tag = v.getTag();
2716 final ShortcutInfo shortcut;
2717 final Intent intent;
2718 if (tag instanceof ShortcutInfo) {
2719 shortcut = (ShortcutInfo) tag;
2720 intent = shortcut.intent;
2721 int[] pos = new int[2];
2722 v.getLocationOnScreen(pos);
2723 intent.setSourceBounds(new Rect(pos[0], pos[1],
2724 pos[0] + v.getWidth(), pos[1] + v.getHeight()));
2725
2726 } else if (tag instanceof AppInfo) {
2727 shortcut = null;
2728 intent = ((AppInfo) tag).intent;
2729 } else {
2730 throw new IllegalArgumentException("Input must be a Shortcut or AppInfo");
2731 }
2732
2733 boolean success = startActivitySafely(v, intent, tag);
2734 mStats.recordLaunch(intent, shortcut);
2735
2736 if (success && v instanceof BubbleTextView) {
2737 mWaitingForResume = (BubbleTextView) v;
2738 mWaitingForResume.setStayPressed(true);
2739 }
2740 }
2741
2742 /**
2743 * Event handler for a folder icon click.
2744 *
2745 * @param v The view that was clicked. Must be an instance of {@link FolderIcon}.
2746 */
2747 protected void onClickFolderIcon(View v) {
2748 if (LOGD) Log.d(TAG, "onClickFolder");
2749 if (!(v instanceof FolderIcon)){
2750 throw new IllegalArgumentException("Input must be a FolderIcon");
2751 }
2752
2753 FolderIcon folderIcon = (FolderIcon) v;
2754 final FolderInfo info = folderIcon.getFolderInfo();
2755 Folder openFolder = mWorkspace.getFolderForTag(info);
2756
2757 // If the folder info reports that the associated folder is open, then verify that
2758 // it is actually opened. There have been a few instances where this gets out of sync.
2759 if (info.opened && openFolder == null) {
2760 Log.d(TAG, "Folder info marked as open, but associated folder is not open. Screen: "
2761 + info.screenId + " (" + info.cellX + ", " + info.cellY + ")");
2762 info.opened = false;
2763 }
2764
2765 if (!info.opened && !folderIcon.getFolder().isDestroyed()) {
2766 // Close any open folder
2767 closeFolder();
2768 // Open the requested folder
2769 openFolder(folderIcon);
2770 } else {
2771 // Find the open folder...
2772 int folderScreen;
2773 if (openFolder != null) {
2774 folderScreen = mWorkspace.getPageForView(openFolder);
2775 // .. and close it
2776 closeFolder(openFolder);
2777 if (folderScreen != mWorkspace.getCurrentPage()) {
2778 // Close any folder open on the current screen
2779 closeFolder();
2780 // Pull the folder onto this screen
2781 openFolder(folderIcon);
2782 }
2783 }
2784 }
2785
2786 if (mLauncherCallbacks != null) {
2787 mLauncherCallbacks.onClickFolderIcon(v);
2788 }
2789 }
2790
2791 /**
2792 * Event handler for the (Add) Widgets button that appears after a long press
2793 * on the home screen.
2794 */
2795 protected void onClickAddWidgetButton(View view) {
2796 if (LOGD) Log.d(TAG, "onClickAddWidgetButton");
2797 if (mIsSafeModeEnabled) {
2798 Toast.makeText(this, R.string.safemode_widget_error, Toast.LENGTH_SHORT).show();
2799 } else {
2800 showWidgetsView(true /* animated */, true /* resetPageToZero */);
2801 if (mLauncherCallbacks != null) {
2802 mLauncherCallbacks.onClickAddWidgetButton(view);
2803 }
2804 }
2805 }
2806
2807 /**
2808 * Event handler for the wallpaper picker button that appears after a long press
2809 * on the home screen.
2810 */
2811 protected void onClickWallpaperPicker(View v) {
2812 if (LOGD) Log.d(TAG, "onClickWallpaperPicker");
2813 startActivityForResult(new Intent(Intent.ACTION_SET_WALLPAPER).setPackage(getPackageName()),
2814 REQUEST_PICK_WALLPAPER);
2815
2816 if (mLauncherCallbacks != null) {
2817 mLauncherCallbacks.onClickWallpaperPicker(v);
2818 }
2819 }
2820
2821 /**
2822 * Event handler for a click on the settings button that appears after a long press
2823 * on the home screen.
2824 */
2825 protected void onClickSettingsButton(View v) {
2826 if (LOGD) Log.d(TAG, "onClickSettingsButton");
2827 if (mLauncherCallbacks != null) {
2828 mLauncherCallbacks.onClickSettingsButton(v);
2829 }
2830 }
2831
2832 public void onTouchDownAllAppsButton(View v) {
2833 // Provide the same haptic feedback that the system offers for virtual keys.
2834 v.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
2835 }
2836
2837 public void performHapticFeedbackOnTouchDown(View v) {
2838 // Provide the same haptic feedback that the system offers for virtual keys.
2839 v.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
2840 }
2841
2842 public View.OnTouchListener getHapticFeedbackTouchListener() {
2843 if (mHapticFeedbackTouchListener == null) {
2844 mHapticFeedbackTouchListener = new View.OnTouchListener() {
2845 @SuppressLint("ClickableViewAccessibility")
2846 @Override
2847 public boolean onTouch(View v, MotionEvent event) {
2848 if ((event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_DOWN) {
2849 v.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
2850 }
2851 return false;
2852 }
2853 };
2854 }
2855 return mHapticFeedbackTouchListener;
2856 }
2857
2858 public void onDragStarted(View view) {
2859 if (isOnCustomContent()) {
2860 // Custom content screen doesn't participate in drag and drop. If on custom
2861 // content screen, move to default.
2862 moveWorkspaceToDefaultScreen();
2863 }
2864
2865 if (mLauncherCallbacks != null) {
2866 mLauncherCallbacks.onDragStarted(view);
2867 }
2868 }
2869
2870 /**
2871 * Called when the user stops interacting with the launcher.
2872 * This implies that the user is now on the homescreen and is not doing housekeeping.
2873 */
2874 protected void onInteractionEnd() {
2875 if (mLauncherCallbacks != null) {
2876 mLauncherCallbacks.onInteractionEnd();
2877 }
2878 }
2879
2880 /**
2881 * Called when the user starts interacting with the launcher.
2882 * The possible interactions are:
2883 * - open all apps
2884 * - reorder an app shortcut, or a widget
2885 * - open the overview mode.
2886 * This is a good time to stop doing things that only make sense
2887 * when the user is on the homescreen and not doing housekeeping.
2888 */
2889 protected void onInteractionBegin() {
2890 if (mLauncherCallbacks != null) {
2891 mLauncherCallbacks.onInteractionBegin();
2892 }
2893 }
2894
2895 /** Updates the interaction state. */
2896 public void updateInteraction(Workspace.State fromState, Workspace.State toState) {
2897 // Only update the interacting state if we are transitioning to/from a view with an
2898 // overlay
2899 boolean fromStateWithOverlay;
2900 boolean toStateWithOverlay;
2901 if (Launcher.DISABLE_ALL_APPS_SEARCH_INTEGRATION) {
2902 fromStateWithOverlay = fromState != Workspace.State.NORMAL;
2903 toStateWithOverlay = toState != Workspace.State.NORMAL;
2904 } else {
2905 fromStateWithOverlay = fromState != Workspace.State.NORMAL &&
2906 fromState != Workspace.State.NORMAL_HIDDEN;
2907 toStateWithOverlay = toState != Workspace.State.NORMAL &&
2908 toState != Workspace.State.NORMAL_HIDDEN;
2909 }
2910 if (toStateWithOverlay) {
2911 onInteractionBegin();
2912 } else if (fromStateWithOverlay) {
2913 onInteractionEnd();
2914 }
2915 }
2916
2917 void startApplicationDetailsActivity(ComponentName componentName, UserHandleCompat user) {
2918 try {
2919 LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(this);
2920 launcherApps.showAppDetailsForProfile(componentName, user);
2921 } catch (SecurityException e) {
2922 Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
2923 Log.e(TAG, "Launcher does not have permission to launch settings");
2924 } catch (ActivityNotFoundException e) {
2925 Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
2926 Log.e(TAG, "Unable to launch settings");
2927 }
2928 }
2929
2930 // returns true if the activity was started
2931 boolean startApplicationUninstallActivity(ComponentName componentName, int flags,
2932 UserHandleCompat user) {
2933 if ((flags & AppInfo.DOWNLOADED_FLAG) == 0) {
2934 // System applications cannot be installed. For now, show a toast explaining that.
2935 // We may give them the option of disabling apps this way.
2936 int messageId = R.string.uninstall_system_app_text;
2937 Toast.makeText(this, messageId, Toast.LENGTH_SHORT).show();
2938 return false;
2939 } else {
2940 String packageName = componentName.getPackageName();
2941 String className = componentName.getClassName();
2942 Intent intent = new Intent(
2943 Intent.ACTION_DELETE, Uri.fromParts("package", packageName, className));
2944 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
2945 Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
2946 if (user != null) {
2947 user.addToIntent(intent, Intent.EXTRA_USER);
2948 }
2949 startActivity(intent);
2950 return true;
2951 }
2952 }
2953
2954 boolean startActivity(View v, Intent intent, Object tag) {
2955 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
2956 try {
2957 // Only launch using the new animation if the shortcut has not opted out (this is a
2958 // private contract between launcher and may be ignored in the future).
2959 boolean useLaunchAnimation = (v != null) &&
2960 !intent.hasExtra(INTENT_EXTRA_IGNORE_LAUNCH_ANIMATION);
2961 LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(this);
2962 UserManagerCompat userManager = UserManagerCompat.getInstance(this);
2963
2964 UserHandleCompat user = null;
2965 if (intent.hasExtra(AppInfo.EXTRA_PROFILE)) {
2966 long serialNumber = intent.getLongExtra(AppInfo.EXTRA_PROFILE, -1);
2967 user = userManager.getUserForSerialNumber(serialNumber);
2968 }
2969
2970 Bundle optsBundle = null;
2971 <<<<<<< MINE
2972 if (useLaunchAnimation) {
2973 ActivityOptions opts = null;
2974 if (sClipRevealMethod != null) {
2975 // TODO: call method directly when Launcher3 can depend on M APIs
2976 int left = 0, top = 0;
2977 int width = v.getMeasuredWidth(), height = v.getMeasuredHeight();
2978 if (v instanceof TextView) {
2979 // Launch from center of icon, not entire view
2980 Drawable icon = Workspace.getTextViewIcon((TextView) v);
2981 if (icon != null) {
2982 Rect bounds = icon.getBounds();
2983 left = (width - bounds.width()) / 2;
2984 top = v.getPaddingTop();
2985 width = bounds.width();
2986 height = bounds.height();
2987 }
2988 }
2989 try {
2990 opts = (ActivityOptions) sClipRevealMethod.invoke(null, v,
2991 left, top, width, height);
2992 } catch (IllegalAccessException e) {
2993 Log.d(TAG, "Could not call makeClipRevealAnimation: " + e);
2994 sClipRevealMethod = null;
2995 } catch (InvocationTargetException e) {
2996 Log.d(TAG, "Could not call makeClipRevealAnimation: " + e);
2997 sClipRevealMethod = null;
2998 }
2999 }
3000 if (opts == null && !Utilities.isLmpOrAbove()) {
3001 opts = ActivityOptions.makeScaleUpAnimation(v, 0, 0,
3002 v.getMeasuredWidth(), v.getMeasuredHeight());
3003 }
3004 optsBundle = opts != null ? opts.toBundle() : null;
3005 ||||||| BASE
3006 if (useLaunchAnimation && !Utilities.isLmpOrAbove()) {
3007 // On pre-L devices, we use the scale up transition.
3008 // Otherwise we use system default.
3009 ActivityOptions opts =
3010 ActivityOptions.makeScaleUpAnimation(v, 0, 0, v.getMeasuredWidth(), v.getMeasured🔵
3011 optsBundle = opts.toBundle();
3012 }
3013
3014 if (user == null || user.equals(UserHandleCompat.myUserHandle())) {
3015 // Could be launching some bookkeeping activity
3016 startActivity(intent, optsBundle);
3017 } else {
3018 // TODO Component can be null when shortcuts are supported for secondary user
3019 launcherApps.startActivityForProfile(intent.getComponent(), user,
3020 intent.getSourceBounds(), optsBundle);
3021 }
3022 return true;
3023 } catch (SecurityException e) {
3024 Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
3025 Log.e(TAG, "Launcher does not have the permission to launch " + intent +
3026 ". Make sure to create a MAIN intent-filter for the corresponding activity " +
3027 "or use the exported attribute for this activity. "
3028 + "tag="+ tag + " intent=" + intent, e);
3029 }
3030 return false;
3031 }
3032 =======
3033 if (useLaunchAnimation && !Utilities.isLmpOrAbove()) {
3034 // On pre-L devices, we use the scale up transition.
3035 ActivityOptions opts =
3036 ActivityOptions.makeScaleUpAnimation(v, 0, 0, v.getMeasuredWidth(), v.getMeasured🔵
3037 optsBundle = opts.toBundle();
3038 } else if (useLaunchAnimation && Utilities.isLmpMr1()) {
3039 // On L-MR1 devices, we use custom slide up animation without a delay
3040 // On L devices, we use the system default slide up.
3041 ActivityOptions opts = ActivityOptions.makeCustomAnimation(this,
3042 R.anim.task_open_enter, R.anim.no_anim);
3043 optsBundle = opts.toBundle();
3044 >>>>>>> YOURS
3045 }
3046
3047 if (user == null || user.equals(UserHandleCompat.myUserHandle())) {
3048 // Could be launching some bookkeeping activity
3049 startActivity(intent, optsBundle);
3050 } else {
3051 // TODO Component can be null when shortcuts are supported for secondary user
3052 launcherApps.startActivityForProfile(intent.getComponent(), user,
3053 intent.getSourceBounds(), optsBundle);
3054 }
3055 return true;
3056 } catch (SecurityException e) {
3057 Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
3058 Log.e(TAG, "Launcher does not have the permission to launch " + intent +
3059 ". Make sure to create a MAIN intent-filter for the corresponding activity " +
3060 "or use the exported attribute for this activity. "
3061 + "tag="+ tag + " intent=" + intent, e);
3062 }
3063 return false;
3064 }
3065
3066 boolean startActivitySafely(View v, Intent intent, Object tag) {
3067 boolean success = false;
3068 if (mIsSafeModeEnabled && !Utilities.isSystemApp(this, intent)) {
3069 Toast.makeText(this, R.string.safemode_shortcut_error, Toast.LENGTH_SHORT).show();
3070 return false;
3071 }
3072 try {
3073 success = startActivity(v, intent, tag);
3074 } catch (ActivityNotFoundException e) {
3075 Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
3076 Log.e(TAG, "Unable to launch. tag=" + tag + " intent=" + intent, e);
3077 }
3078 return success;
3079 }
3080
3081 /**
3082 * This method draws the FolderIcon to an ImageView and then adds and positions that ImageView
3083 * in the DragLayer in the exact absolute location of the original FolderIcon.
3084 */
3085 private void copyFolderIconToImage(FolderIcon fi) {
3086 final int width = fi.getMeasuredWidth();
3087 final int height = fi.getMeasuredHeight();
3088
3089 // Lazy load ImageView, Bitmap and Canvas
3090 if (mFolderIconImageView == null) {
3091 mFolderIconImageView = new ImageView(this);
3092 }
3093 if (mFolderIconBitmap == null || mFolderIconBitmap.getWidth() != width ||
3094 mFolderIconBitmap.getHeight() != height) {
3095 mFolderIconBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
3096 mFolderIconCanvas = new Canvas(mFolderIconBitmap);
3097 }
3098
3099 DragLayer.LayoutParams lp;
3100 if (mFolderIconImageView.getLayoutParams() instanceof DragLayer.LayoutParams) {
3101 lp = (DragLayer.LayoutParams) mFolderIconImageView.getLayoutParams();
3102 } else {
3103 lp = new DragLayer.LayoutParams(width, height);
3104 }
3105
3106 // The layout from which the folder is being opened may be scaled, adjust the starting
3107 // view size by this scale factor.
3108 float scale = mDragLayer.getDescendantRectRelativeToSelf(fi, mRectForFolderAnimation);
3109 lp.customPosition = true;
3110 lp.x = mRectForFolderAnimation.left;
3111 lp.y = mRectForFolderAnimation.top;
3112 lp.width = (int) (scale * width);
3113 lp.height = (int) (scale * height);
3114
3115 mFolderIconCanvas.drawColor(0, PorterDuff.Mode.CLEAR);
3116 fi.draw(mFolderIconCanvas);
3117 mFolderIconImageView.setImageBitmap(mFolderIconBitmap);
3118 if (fi.getFolder() != null) {
3119 mFolderIconImageView.setPivotX(fi.getFolder().getPivotXForIconAnimation());
3120 mFolderIconImageView.setPivotY(fi.getFolder().getPivotYForIconAnimation());
3121 }
3122 // Just in case this image view is still in the drag layer from a previous animation,
3123 // we remove it and re-add it.
3124 if (mDragLayer.indexOfChild(mFolderIconImageView) != -1) {
3125 mDragLayer.removeView(mFolderIconImageView);
3126 }
3127 mDragLayer.addView(mFolderIconImageView, lp);
3128 if (fi.getFolder() != null) {
3129 fi.getFolder().bringToFront();
3130 }
3131 }
3132
3133 private void growAndFadeOutFolderIcon(FolderIcon fi) {
3134 if (fi == null) return;
3135 PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 0);
3136 PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 1.5f);
3137 PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 1.5f);
3138
3139 FolderInfo info = (FolderInfo) fi.getTag();
3140 if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
3141 CellLayout cl = (CellLayout) fi.getParent().getParent();
3142 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) fi.getLayoutParams();
3143 cl.setFolderLeaveBehindCell(lp.cellX, lp.cellY);
3144 }
3145
3146 // Push an ImageView copy of the FolderIcon into the DragLayer and hide the original
3147 copyFolderIconToImage(fi);
3148 fi.setVisibility(View.INVISIBLE);
3149
3150 ObjectAnimator oa = LauncherAnimUtils.ofPropertyValuesHolder(mFolderIconImageView, alpha,
3151 scaleX, scaleY);
3152 if (Utilities.isLmpOrAbove()) {
3153 oa.setInterpolator(new LogDecelerateInterpolator(100, 0));
3154 }
3155 oa.setDuration(getResources().getInteger(R.integer.config_folderExpandDuration));
3156 oa.start();
3157 }
3158
3159 private void shrinkAndFadeInFolderIcon(final FolderIcon fi) {
3160 if (fi == null) return;
3161 PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 1.0f);
3162 PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 1.0f);
3163 PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 1.0f);
3164
3165 final CellLayout cl = (CellLayout) fi.getParent().getParent();
3166
3167 // We remove and re-draw the FolderIcon in-case it has changed
3168 mDragLayer.removeView(mFolderIconImageView);
3169 copyFolderIconToImage(fi);
3170 ObjectAnimator oa = LauncherAnimUtils.ofPropertyValuesHolder(mFolderIconImageView, alpha,
3171 scaleX, scaleY);
3172 oa.setDuration(getResources().getInteger(R.integer.config_folderExpandDuration));
3173 oa.addListener(new AnimatorListenerAdapter() {
3174 @Override
3175 public void onAnimationEnd(Animator animation) {
3176 if (cl != null) {
3177 cl.clearFolderLeaveBehind();
3178 // Remove the ImageView copy of the FolderIcon and make the original visible.
3179 mDragLayer.removeView(mFolderIconImageView);
3180 fi.setVisibility(View.VISIBLE);
3181 }
3182 }
3183 });
3184 oa.start();
3185 }
3186
3187 /**
3188 * Opens the user folder described by the specified tag. The opening of the folder
3189 * is animated relative to the specified View. If the View is null, no animation
3190 * is played.
3191 *
3192 * @param folderInfo The FolderInfo describing the folder to open.
3193 */
3194 public void openFolder(FolderIcon folderIcon) {
3195 Folder folder = folderIcon.getFolder();
3196 Folder openFolder = mWorkspace != null ? mWorkspace.getOpenFolder() : null;
3197 if (openFolder != null && openFolder != folder) {
3198 // Close any open folder before opening a folder.
3199 closeFolder();
3200 }
3201
3202 FolderInfo info = folder.mInfo;
3203
3204 info.opened = true;
3205
3206 // While the folder is open, the position of the icon cannot change.
3207 ((CellLayout.LayoutParams) folderIcon.getLayoutParams()).canReorder = false;
3208
3209 // Just verify that the folder hasn't already been added to the DragLayer.
3210 // There was a one-off crash where the folder had a parent already.
3211 if (folder.getParent() == null) {
3212 mDragLayer.addView(folder);
3213 mDragController.addDropTarget((DropTarget) folder);
3214 } else {
3215 Log.w(TAG, "Opening folder (" + folder + ") which already has a parent (" +
3216 folder.getParent() + ").");
3217 }
3218 folder.animateOpen();
3219 growAndFadeOutFolderIcon(folderIcon);
3220
3221 // Notify the accessibility manager that this folder "window" has appeared and occluded
3222 // the workspace items
3223 folder.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
3224 getDragLayer().sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
3225 }
3226
3227 public void closeFolder() {
3228 Folder folder = mWorkspace != null ? mWorkspace.getOpenFolder() : null;
3229 if (folder != null) {
3230 if (folder.isEditingName()) {
3231 folder.dismissEditingName();
3232 }
3233 closeFolder(folder);
3234 }
3235 }
3236
3237 public void closeFolder(Folder folder) {
3238 folder.getInfo().opened = false;
3239
3240 ViewGroup parent = (ViewGroup) folder.getParent().getParent();
3241 if (parent != null) {
3242 FolderIcon fi = (FolderIcon) mWorkspace.getViewForTag(folder.mInfo);
3243 shrinkAndFadeInFolderIcon(fi);
3244 if (fi != null) {
3245 ((CellLayout.LayoutParams) fi.getLayoutParams()).canReorder = true;
3246 }
3247 }
3248 folder.animateClosed();
3249
3250 // Notify the accessibility manager that this folder "window" has disappeard and no
3251 // longer occludeds the workspace items
3252 getDragLayer().sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
3253 }
3254
3255 public boolean onLongClick(View v) {
3256 if (!isDraggingEnabled()) return false;
3257 if (isWorkspaceLocked()) return false;
3258 if (mState != State.WORKSPACE) return false;
3259
3260 if (v instanceof Workspace) {
3261 if (!mWorkspace.isInOverviewMode()) {
3262 if (!mWorkspace.isTouchActive()) {
3263 showOverviewMode(true);
3264 mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
3265 HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
3266 return true;
3267 } else {
3268 return false;
3269 }
3270 } else {
3271 return false;
3272 }
3273 }
3274
3275 CellLayout.CellInfo longClickCellInfo = null;
3276 View itemUnderLongClick = null;
3277 if (v.getTag() instanceof ItemInfo) {
3278 ItemInfo info = (ItemInfo) v.getTag();
3279 longClickCellInfo = new CellLayout.CellInfo(v, info);
3280 itemUnderLongClick = longClickCellInfo.cell;
3281 resetAddInfo();
3282 }
3283
3284 // The hotseat touch handling does not go through Workspace, and we always allow long press
3285 // on hotseat items.
3286 final boolean inHotseat = isHotseatLayout(v);
3287 if (!mDragController.isDragging()) {
3288 if (itemUnderLongClick == null) {
3289 // User long pressed on empty space
3290 mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
3291 HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
3292 if (mWorkspace.isInOverviewMode()) {
3293 mWorkspace.startReordering(v);
3294 } else {
3295 showOverviewMode(true);
3296 }
3297 } else {
3298 final boolean isAllAppsButton = inHotseat && isAllAppsButtonRank(
3299 mHotseat.getOrderInHotseat(
3300 longClickCellInfo.cellX,
3301 longClickCellInfo.cellY));
3302 if (!(itemUnderLongClick instanceof Folder || isAllAppsButton)) {
3303 // User long pressed on an item
3304 mWorkspace.startDrag(longClickCellInfo);
3305 }
3306 }
3307 }
3308 return true;
3309 }
3310
3311 boolean isHotseatLayout(View layout) {
3312 return mHotseat != null && layout != null &&
3313 (layout instanceof CellLayout) && (layout == mHotseat.getLayout());
3314 }
3315
3316 /**
3317 * Returns the CellLayout of the specified container at the specified screen.
3318 */
3319 public CellLayout getCellLayout(long container, long screenId) {
3320 if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
3321 if (mHotseat != null) {
3322 return mHotseat.getLayout();
3323 } else {
3324 return null;
3325 }
3326 } else {
3327 return mWorkspace.getScreenWithId(screenId);
3328 }
3329 }
3330
3331 /**
3332 * For overridden classes.
3333 */
3334 public boolean isAllAppsVisible() {
3335 return isAppsViewVisible();
3336 }
3337
3338 public boolean isAppsViewVisible() {
3339 return (mState == State.APPS) || (mOnResumeState == State.APPS);
3340 }
3341
3342 public boolean isWidgetsViewVisible() {
3343 return (mState == State.WIDGETS) || (mOnResumeState == State.WIDGETS);
3344 }
3345
3346 private void setWorkspaceBackground(int background) {
3347 switch (background) {
3348 case WORKSPACE_BACKGROUND_TRANSPARENT:
3349 getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
3350 break;
3351 case WORKSPACE_BACKGROUND_BLACK:
3352 getWindow().setBackgroundDrawable(null);
3353 break;
3354 default:
3355 getWindow().setBackgroundDrawable(mWorkspaceBackgroundDrawable);
3356 }
3357 }
3358
3359 protected void changeWallpaperVisiblity(boolean visible) {
3360 int wpflags = visible ? WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER : 0;
3361 int curflags = getWindow().getAttributes().flags
3362 & WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
3363 if (wpflags != curflags) {
3364 getWindow().setFlags(wpflags, WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER);
3365 }
3366 setWorkspaceBackground(visible ? WORKSPACE_BACKGROUND_GRADIENT : WORKSPACE_BACKGROUND_BLACK);
3367 }
3368
3369 @Override
3370 public void onTrimMemory(int level) {
3371 super.onTrimMemory(level);
3372 if (level >= ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
3373 // The widget preview db can result in holding onto over
3374 // 3MB of memory for caching which isn't necessary.
3375 SQLiteDatabase.releaseMemory();
3376
3377 // This clears all widget bitmaps from the widget tray
3378 // TODO(hyunyoungs)
3379 }
3380 if (mLauncherCallbacks != null) {
3381 mLauncherCallbacks.onTrimMemory(level);
3382 }
3383 }
3384
3385 @Override
3386 public void onStateTransitionHideSearchBar() {
3387 // Hide the search bar
3388 if (mSearchDropTargetBar != null) {
3389 mSearchDropTargetBar.hideSearchBar(false /* animated */);
3390 }
3391 }
3392
3393 public void showWorkspace(boolean animated) {
3394 showWorkspace(WorkspaceStateTransitionAnimation.SCROLL_TO_CURRENT_PAGE, animated, null,
3395 true);
3396 }
3397
3398 public void showWorkspace(boolean animated, Runnable onCompleteRunnable) {
3399 showWorkspace(WorkspaceStateTransitionAnimation.SCROLL_TO_CURRENT_PAGE, animated,
3400 onCompleteRunnable, true);
3401 }
3402
3403 protected void showWorkspace(int snapToPage, boolean animated) {
3404 showWorkspace(snapToPage, animated, null, true);
3405 }
3406
3407 void showWorkspace(int snapToPage, boolean animated, Runnable onCompleteRunnable,
3408 boolean notifyLauncherCallbacks) {
3409 boolean changed = mState != State.WORKSPACE ||
3410 mWorkspace.getState() != Workspace.State.NORMAL;
3411 if (changed) {
3412 boolean wasInSpringLoadedMode = (mState != State.WORKSPACE);
3413 mWorkspace.setVisibility(View.VISIBLE);
3414 mStateTransitionAnimation.startAnimationToWorkspace(mState, Workspace.State.NORMAL,
3415 snapToPage, animated, onCompleteRunnable);
3416
3417 // Show the search bar (only animate if we were showing the drop target bar in spring
3418 // loaded mode)
3419 if (mSearchDropTargetBar != null) {
3420 mSearchDropTargetBar.showSearchBar(animated && wasInSpringLoadedMode);
3421 }
3422
3423 // Set focus to the AppsCustomize button
3424 if (mAllAppsButton != null) {
3425 mAllAppsButton.requestFocus();
3426 }
3427 }
3428
3429 // Change the state *after* we've called all the transition code
3430 mState = State.WORKSPACE;
3431
3432 // Resume the auto-advance of widgets
3433 mUserPresent = true;
3434 updateAutoAdvanceState();
3435
3436 if (changed) {
3437 // Send an accessibility event to announce the context change
3438 getWindow().getDecorView()
3439 .sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
3440 if (notifyLauncherCallbacks) {
3441 // Dismiss all apps when the workspace is shown
3442 if (!Launcher.DISABLE_ALL_APPS_SEARCH_INTEGRATION && mLauncherCallbacks != null) {
3443 mLauncherCallbacks.onAllAppsHidden();
3444 }
3445 }
3446 }
3447 }
3448
3449 void showOverviewMode(boolean animated) {
3450 mWorkspace.setVisibility(View.VISIBLE);
3451 mStateTransitionAnimation.startAnimationToWorkspace(mState, Workspace.State.OVERVIEW,
3452 WorkspaceStateTransitionAnimation.SCROLL_TO_CURRENT_PAGE, animated,
3453 null /* onCompleteRunnable */);
3454 mState = State.WORKSPACE;
3455 }
3456
3457 /**
3458 * Shows the apps view.
3459 */
3460 void showAppsView(boolean animated, boolean resetListToTop, boolean updatePredictedApps) {
3461 if (resetListToTop) {
3462 mAppsView.scrollToTop();
3463 }
3464 if (updatePredictedApps) {
3465 tryAndUpdatePredictedApps();
3466 }
3467 showAppsOrWidgets(animated, State.APPS);
3468 }
3469
3470 /**
3471 * Shows the widgets view.
3472 */
3473 void showWidgetsView(boolean animated, boolean resetPageToZero) {
3474 if (LOGD) Log.d(TAG, "showWidgetsView:" + animated + " resetPageToZero:" + resetPageToZero);
3475 if (resetPageToZero) {
3476 mWidgetsView.scrollToTop();
3477 }
3478 showAppsOrWidgets(animated, State.WIDGETS);
3479
3480 mWidgetsView.post(new Runnable() {
3481 @Override
3482 public void run() {
3483 mWidgetsView.requestFocus();
3484 }
3485 });
3486 }
3487
3488 /**
3489 * Sets up the transition to show the apps/widgets view.
3490 *
3491 * @return whether the current from and to state allowed this operation
3492 */
3493 // TODO: calling method should use the return value so that when {@code false} is returned
3494 // the workspace transition doesn't fall into invalid state.
3495 private boolean showAppsOrWidgets(boolean animated, State toState) {
3496 if (mState != State.WORKSPACE && mState != State.APPS_SPRING_LOADED &&
3497 mState != State.WIDGETS_SPRING_LOADED) {
3498 return false;
3499 }
3500 if (toState != State.APPS && toState != State.WIDGETS) {
3501 return false;
3502 }
3503
3504 if (toState == State.APPS) {
3505 mStateTransitionAnimation.startAnimationToAllApps(animated);
3506 if (!Launcher.DISABLE_ALL_APPS_SEARCH_INTEGRATION && mLauncherCallbacks != null) {
3507 mLauncherCallbacks.onAllAppsShown();
3508 }
3509 } else {
3510 mStateTransitionAnimation.startAnimationToWidgets(animated);
3511 }
3512
3513 // Change the state *after* we've called all the transition code
3514 mState = toState;
3515
3516 // Pause the auto-advance of widgets until we are out of AllApps
3517 mUserPresent = false;
3518 updateAutoAdvanceState();
3519 closeFolder();
3520
3521 // Send an accessibility event to announce the context change
3522 getWindow().getDecorView()
3523 .sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
3524 return true;
3525 }
3526
3527 /**
3528 * Updates the workspace and interaction state on state change, and return the animation to this
3529 * new state.
3530 */
3531 public Animator startWorkspaceStateChangeAnimation(Workspace.State toState, int toPage,
3532 boolean animated, HashMap<View, Integer> layerViews) {
3533 Workspace.State fromState = mWorkspace.getState();
3534 Animator anim = mWorkspace.setStateWithAnimation(toState, toPage, animated, layerViews);
3535 updateInteraction(fromState, toState);
3536 return anim;
3537 }
3538
3539 public void enterSpringLoadedDragMode() {
3540 if (LOGD) Log.d(TAG, String.format("enterSpringLoadedDragMode [mState=%s", mState.name()));
3541 if (mState == State.WORKSPACE || mState == State.APPS_SPRING_LOADED ||
3542 mState == State.WIDGETS_SPRING_LOADED) {
3543 return;
3544 }
3545
3546 mStateTransitionAnimation.startAnimationToWorkspace(mState, Workspace.State.SPRING_LOADED,
3547 WorkspaceStateTransitionAnimation.SCROLL_TO_CURRENT_PAGE, true /* animated */,
3548 null /* onCompleteRunnable */);
3549 mState = isAppsViewVisible() ? State.APPS_SPRING_LOADED : State.WIDGETS_SPRING_LOADED;
3550 }
3551
3552 public void exitSpringLoadedDragModeDelayed(final boolean successfulDrop, int delay,
3553 final Runnable onCompleteRunnable) {
3554 if (mState != State.APPS_SPRING_LOADED && mState != State.WIDGETS_SPRING_LOADED) return;
3555
3556 if (successfulDrop) {
3557 // We need to trigger all apps hidden to notify search to update itself before the
3558 // delayed call to showWorkspace below
3559 if (!Launcher.DISABLE_ALL_APPS_SEARCH_INTEGRATION && mLauncherCallbacks != null) {
3560 mLauncherCallbacks.onAllAppsHidden();
3561 }
3562 }
3563
3564 mHandler.postDelayed(new Runnable() {
3565 @Override
3566 public void run() {
3567 if (successfulDrop) {
3568 // TODO(hyunyoungs): verify if this hack is still needed, if not, delete.
3569 //
3570 // Before we show workspace, hide all apps again because
3571 // exitSpringLoadedDragMode made it visible. This is a bit hacky; we should
3572 // clean up our state transition functions
3573 mWidgetsView.setVisibility(View.GONE);
3574 showWorkspace(true, onCompleteRunnable);
3575 } else {
3576 exitSpringLoadedDragMode();
3577 }
3578 }
3579 }, delay);
3580 }
3581
3582 void exitSpringLoadedDragMode() {
3583 if (mState == State.APPS_SPRING_LOADED) {
3584 showAppsView(true /* animated */, false /* resetListToTop */,
3585 false /* updatePredictedApps */);
3586 } else if (mState == State.WIDGETS_SPRING_LOADED) {
3587 showWidgetsView(true, false);
3588 }
3589 }
3590
3591 /**
3592 * Updates the set of predicted apps if it hasn't been updated since the last time Launcher was
3593 * resumed.
3594 */
3595 private void tryAndUpdatePredictedApps() {
3596 if (mLauncherCallbacks != null) {
3597 List<ComponentName> apps = mLauncherCallbacks.getPredictedApps();
3598 if (!apps.isEmpty()) {
3599 mAppsView.setPredictedApps(apps);
3600 }
3601 }
3602 }
3603
3604 void lockAllApps() {
3605 // TODO
3606 }
3607
3608 void unlockAllApps() {
3609 // TODO
3610 }
3611
3612 protected void disableVoiceButtonProxy(boolean disable) {
3613 // NO-OP
3614 }
3615
3616 public View getOrCreateQsbBar() {
3617 if (mLauncherCallbacks != null && mLauncherCallbacks.providesSearch()) {
3618 return mLauncherCallbacks.getQsbBar();
3619 }
3620
3621 if (mQsb == null) {
3622 AppWidgetProviderInfo searchProvider = Utilities.getSearchWidgetProvider(this);
3623 if (searchProvider == null) {
3624 return null;
3625 }
3626
3627 Bundle opts = new Bundle();
3628 opts.putInt(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY,
3629 AppWidgetProviderInfo.WIDGET_CATEGORY_SEARCHBOX);
3630
3631 SharedPreferences sp = getSharedPreferences(
3632 LauncherAppState.getSharedPreferencesKey(), MODE_PRIVATE);
3633 int widgetId = sp.getInt(QSB_WIDGET_ID, -1);
3634 AppWidgetProviderInfo widgetInfo = mAppWidgetManager.getAppWidgetInfo(widgetId);
3635 if (!searchProvider.provider.flattenToString().equals(
3636 sp.getString(QSB_WIDGET_PROVIDER, null))
3637 || (widgetInfo == null)
3638 || !widgetInfo.provider.equals(searchProvider.provider)) {
3639 // A valid widget is not already bound.
3640 if (widgetId > -1) {
3641 mAppWidgetHost.deleteAppWidgetId(widgetId);
3642 widgetId = -1;
3643 }
3644
3645 // Try to bind a new widget
3646 widgetId = mAppWidgetHost.allocateAppWidgetId();
3647
3648 if (!AppWidgetManagerCompat.getInstance(this)
3649 .bindAppWidgetIdIfAllowed(widgetId, searchProvider, opts)) {
3650 mAppWidgetHost.deleteAppWidgetId(widgetId);
3651 widgetId = -1;
3652 }
3653
3654 sp.edit()
3655 .putInt(QSB_WIDGET_ID, widgetId)
3656 .putString(QSB_WIDGET_PROVIDER, searchProvider.provider.flattenToString())
3657 .commit();
3658 }
3659
3660 if (widgetId != -1) {
3661 mQsb = mAppWidgetHost.createView(this, widgetId, searchProvider);
3662 mQsb.updateAppWidgetOptions(opts);
3663 mQsb.setPadding(0, 0, 0, 0);
3664 mSearchDropTargetBar.addView(mQsb);
3665 mSearchDropTargetBar.setQsbSearchBar(mQsb);
3666 }
3667 }
3668 return mQsb;
3669 }
3670
3671 private void reinflateQSBIfNecessary() {
3672 if (mQsb instanceof LauncherAppWidgetHostView &&
3673 ((LauncherAppWidgetHostView) mQsb).isReinflateRequired()) {
3674 mSearchDropTargetBar.removeView(mQsb);
3675 mQsb = null;
3676 mSearchDropTargetBar.setQsbSearchBar(getOrCreateQsbBar());
3677 }
3678 }
3679
3680 @Override
3681 public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
3682 final boolean result = super.dispatchPopulateAccessibilityEvent(event);
3683 final List<CharSequence> text = event.getText();
3684 text.clear();
3685 // Populate event with a fake title based on the current state.
3686 if (mState == State.APPS) {
3687 text.add(getString(R.string.all_apps_button_label));
3688 } else if (mState == State.WIDGETS) {
3689 text.add(getString(R.string.widget_button_text));
3690 } else {
3691 text.add(getString(R.string.all_apps_home_button_label));
3692 }
3693 return result;
3694 }
3695
3696 /**
3697 * Receives notifications when system dialogs are to be closed.
3698 */
3699 @Thunk class CloseSystemDialogsIntentReceiver extends BroadcastReceiver {
3700 @Override
3701 public void onReceive(Context context, Intent intent) {
3702 closeSystemDialogs();
3703 }
3704 }
3705
3706 /**
3707 * Receives notifications whenever the appwidgets are reset.
3708 */
3709 private class AppWidgetResetObserver extends ContentObserver {
3710 public AppWidgetResetObserver() {
3711 super(new Handler());
3712 }
3713
3714 @Override
3715 public void onChange(boolean selfChange) {
3716 onAppWidgetReset();
3717 }
3718 }
3719
3720 /**
3721 * If the activity is currently paused, signal that we need to run the passed Runnable
3722 * in onResume.
3723 *
3724 * This needs to be called from incoming places where resources might have been loaded
3725 * while the activity is paused. That is because the Configuration (e.g., rotation) might be
3726 * wrong when we're not running, and if the activity comes back to what the configuration was
3727 * when we were paused, activity is not restarted.
3728 *
3729 * Implementation of the method from LauncherModel.Callbacks.
3730 *
3731 * @return {@code true} if we are currently paused. The caller might be able to skip some work
3732 */
3733 private boolean waitUntilResume(Runnable run, boolean deletePreviousRunnables) {
3734 if (mPaused) {
3735 if (LOGD) Log.d(TAG, "Deferring update until onResume");
3736 if (deletePreviousRunnables) {
3737 while (mBindOnResumeCallbacks.remove(run)) {
3738 }
3739 }
3740 mBindOnResumeCallbacks.add(run);
3741 return true;
3742 } else {
3743 return false;
3744 }
3745 }
3746
3747 private boolean waitUntilResume(Runnable run) {
3748 return waitUntilResume(run, false);
3749 }
3750
3751 public void addOnResumeCallback(Runnable run) {
3752 mOnResumeCallbacks.add(run);
3753 }
3754
3755 /**
3756 * If the activity is currently paused, signal that we need to re-run the loader
3757 * in onResume.
3758 *
3759 * This needs to be called from incoming places where resources might have been loaded
3760 * while we are paused. That is becaues the Configuration might be wrong
3761 * when we're not running, and if it comes back to what it was when we
3762 * were paused, we are not restarted.
3763 *
3764 * Implementation of the method from LauncherModel.Callbacks.
3765 *
3766 * @return true if we are currently paused. The caller might be able to
3767 * skip some work in that case since we will come back again.
3768 */
3769 public boolean setLoadOnResume() {
3770 if (mPaused) {
3771 if (LOGD) Log.d(TAG, "setLoadOnResume");
3772 mOnResumeNeedsLoad = true;
3773 return true;
3774 } else {
3775 return false;
3776 }
3777 }
3778
3779 /**
3780 * Implementation of the method from LauncherModel.Callbacks.
3781 */
3782 public int getCurrentWorkspaceScreen() {
3783 if (mWorkspace != null) {
3784 return mWorkspace.getCurrentPage();
3785 } else {
3786 return SCREEN_COUNT / 2;
3787 }
3788 }
3789
3790 /**
3791 * Refreshes the shortcuts shown on the workspace.
3792 *
3793 * Implementation of the method from LauncherModel.Callbacks.
3794 */
3795 public void startBinding() {
3796 setWorkspaceLoading(true);
3797
3798 // If we're starting binding all over again, clear any bind calls we'd postponed in
3799 // the past (see waitUntilResume) -- we don't need them since we're starting binding
3800 // from scratch again
3801 mBindOnResumeCallbacks.clear();
3802
3803 // Clear the workspace because it's going to be rebound
3804 mWorkspace.clearDropTargets();
3805 mWorkspace.removeAllWorkspaceScreens();
3806
3807 mWidgetsToAdvance.clear();
3808 if (mHotseat != null) {
3809 mHotseat.resetLayout();
3810 }
3811 }
3812
3813 @Override
3814 public void bindScreens(ArrayList<Long> orderedScreenIds) {
3815 bindAddScreens(orderedScreenIds);
3816
3817 // If there are no screens, we need to have an empty screen
3818 if (orderedScreenIds.size() == 0) {
3819 mWorkspace.addExtraEmptyScreen();
3820 }
3821
3822 // Create the custom content page (this call updates mDefaultScreen which calls
3823 // setCurrentPage() so ensure that all pages are added before calling this).
3824 if (hasCustomContentToLeft()) {
3825 mWorkspace.createCustomContentContainer();
3826 populateCustomContentContainer();
3827 }
3828 }
3829
3830 @Override
3831 public void bindAddScreens(ArrayList<Long> orderedScreenIds) {
3832 // Log to disk
3833 Launcher.addDumpLog(TAG, "11683562 - bindAddScreens()", true);
3834 Launcher.addDumpLog(TAG, "11683562 - orderedScreenIds: " +
3835 TextUtils.join(", ", orderedScreenIds), true);
3836 int count = orderedScreenIds.size();
3837 for (int i = 0; i < count; i++) {
3838 mWorkspace.insertNewWorkspaceScreenBeforeEmptyScreen(orderedScreenIds.get(i));
3839 }
3840 }
3841
3842 private boolean shouldShowWeightWatcher() {
3843 String spKey = LauncherAppState.getSharedPreferencesKey();
3844 SharedPreferences sp = getSharedPreferences(spKey, Context.MODE_PRIVATE);
3845 boolean show = sp.getBoolean(SHOW_WEIGHT_WATCHER, SHOW_WEIGHT_WATCHER_DEFAULT);
3846
3847 return show;
3848 }
3849
3850 private void toggleShowWeightWatcher() {
3851 String spKey = LauncherAppState.getSharedPreferencesKey();
3852 SharedPreferences sp = getSharedPreferences(spKey, Context.MODE_PRIVATE);
3853 boolean show = sp.getBoolean(SHOW_WEIGHT_WATCHER, true);
3854
3855 show = !show;
3856
3857 SharedPreferences.Editor editor = sp.edit();
3858 editor.putBoolean(SHOW_WEIGHT_WATCHER, show);
3859 editor.commit();
3860
3861 if (mWeightWatcher != null) {
3862 mWeightWatcher.setVisibility(show ? View.VISIBLE : View.GONE);
3863 }
3864 }
3865
3866 public void bindAppsAdded(final ArrayList<Long> newScreens,
3867 final ArrayList<ItemInfo> addNotAnimated,
3868 final ArrayList<ItemInfo> addAnimated,
3869 final ArrayList<AppInfo> addedApps) {
3870 Runnable r = new Runnable() {
3871 public void run() {
3872 bindAppsAdded(newScreens, addNotAnimated, addAnimated, addedApps);
3873 }
3874 };
3875 if (waitUntilResume(r)) {
3876 return;
3877 }
3878
3879 // Add the new screens
3880 if (newScreens != null) {
3881 bindAddScreens(newScreens);
3882 }
3883
3884 // We add the items without animation on non-visible pages, and with
3885 // animations on the new page (which we will try and snap to).
3886 if (addNotAnimated != null && !addNotAnimated.isEmpty()) {
3887 bindItems(addNotAnimated, 0,
3888 addNotAnimated.size(), false);
3889 }
3890 if (addAnimated != null && !addAnimated.isEmpty()) {
3891 bindItems(addAnimated, 0,
3892 addAnimated.size(), true);
3893 }
3894
3895 // Remove the extra empty screen
3896 mWorkspace.removeExtraEmptyScreen(false, false);
3897
3898 if (addedApps != null && mAppsView != null) {
3899 mAppsView.addApps(addedApps);
3900 }
3901 }
3902
3903 /**
3904 * Bind the items start-end from the list.
3905 *
3906 * Implementation of the method from LauncherModel.Callbacks.
3907 */
3908 public void bindItems(final ArrayList<ItemInfo> shortcuts, final int start, final int end,
3909 final boolean forceAnimateIcons) {
3910 Runnable r = new Runnable() {
3911 public void run() {
3912 bindItems(shortcuts, start, end, forceAnimateIcons);
3913 }
3914 };
3915 if (waitUntilResume(r)) {
3916 return;
3917 }
3918
3919 // Get the list of added shortcuts and intersect them with the set of shortcuts here
3920 final AnimatorSet anim = LauncherAnimUtils.createAnimatorSet();
3921 final Collection<Animator> bounceAnims = new ArrayList<Animator>();
3922 final boolean animateIcons = forceAnimateIcons && canRunNewAppsAnimation();
3923 Workspace workspace = mWorkspace;
3924 long newShortcutsScreenId = -1;
3925 for (int i = start; i < end; i++) {
3926 final ItemInfo item = shortcuts.get(i);
3927
3928 // Short circuit if we are loading dock items for a configuration which has no dock
3929 if (item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT &&
3930 mHotseat == null) {
3931 continue;
3932 }
3933
3934 switch (item.itemType) {
3935 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
3936 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
3937 ShortcutInfo info = (ShortcutInfo) item;
3938 View shortcut = createShortcut(info);
3939
3940 /*
3941 * TODO: FIX collision case
3942 */
3943 if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
3944 CellLayout cl = mWorkspace.getScreenWithId(item.screenId);
3945 if (cl != null && cl.isOccupied(item.cellX, item.cellY)) {
3946 View v = cl.getChildAt(item.cellX, item.cellY);
3947 Object tag = v.getTag();
3948 String desc = "Collision while binding workspace item: " + item
3949 + ". Collides with " + tag;
3950 if (LauncherAppState.isDogfoodBuild()) {
3951 throw (new RuntimeException(desc));
3952 } else {
3953 Log.d(TAG, desc);
3954 }
3955 }
3956 }
3957
3958 workspace.addInScreenFromBind(shortcut, item.container, item.screenId, item.cellX,
3959 item.cellY, 1, 1);
3960 if (animateIcons) {
3961 // Animate all the applications up now
3962 shortcut.setAlpha(0f);
3963 shortcut.setScaleX(0f);
3964 shortcut.setScaleY(0f);
3965 bounceAnims.add(createNewAppBounceAnimation(shortcut, i));
3966 newShortcutsScreenId = item.screenId;
3967 }
3968 break;
3969 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
3970 FolderIcon newFolder = FolderIcon.fromXml(R.layout.folder_icon, this,
3971 (ViewGroup) workspace.getChildAt(workspace.getCurrentPage()),
3972 (FolderInfo) item, mIconCache);
3973 workspace.addInScreenFromBind(newFolder, item.container, item.screenId, item.cellX,
3974 item.cellY, 1, 1);
3975 break;
3976 default:
3977 throw new RuntimeException("Invalid Item Type");
3978 }
3979 }
3980
3981 if (animateIcons) {
3982 // Animate to the correct page
3983 if (newShortcutsScreenId > -1) {
3984 long currentScreenId = mWorkspace.getScreenIdForPageIndex(mWorkspace.getNextPage());
3985 final int newScreenIndex = mWorkspace.getPageIndexForScreenId(newShortcutsScreenId);
3986 final Runnable startBounceAnimRunnable = new Runnable() {
3987 public void run() {
3988 anim.playTogether(bounceAnims);
3989 anim.start();
3990 }
3991 };
3992 if (newShortcutsScreenId != currentScreenId) {
3993 // We post the animation slightly delayed to prevent slowdowns
3994 // when we are loading right after we return to launcher.
3995 mWorkspace.postDelayed(new Runnable() {
3996 public void run() {
3997 if (mWorkspace != null) {
3998 mWorkspace.snapToPage(newScreenIndex);
3999 mWorkspace.postDelayed(startBounceAnimRunnable,
4000 NEW_APPS_ANIMATION_DELAY);
4001 }
4002 }
4003 }, NEW_APPS_PAGE_MOVE_DELAY);
4004 } else {
4005 mWorkspace.postDelayed(startBounceAnimRunnable, NEW_APPS_ANIMATION_DELAY);
4006 }
4007 }
4008 }
4009 workspace.requestLayout();
4010 }
4011
4012 /**
4013 * Implementation of the method from LauncherModel.Callbacks.
4014 */
4015 public void bindFolders(final LongArrayMap<FolderInfo> folders) {
4016 Runnable r = new Runnable() {
4017 public void run() {
4018 bindFolders(folders);
4019 }
4020 };
4021 if (waitUntilResume(r)) {
4022 return;
4023 }
4024 sFolders = folders.clone();
4025 }
4026
4027 /**
4028 * Add the views for a widget to the workspace.
4029 *
4030 * Implementation of the method from LauncherModel.Callbacks.
4031 */
4032 public void bindAppWidget(final LauncherAppWidgetInfo item) {
4033 Runnable r = new Runnable() {
4034 public void run() {
4035 bindAppWidget(item);
4036 }
4037 };
4038 if (waitUntilResume(r)) {
4039 return;
4040 }
4041
4042 final long start = DEBUG_WIDGETS ? SystemClock.uptimeMillis() : 0;
4043 if (DEBUG_WIDGETS) {
4044 Log.d(TAG, "bindAppWidget: " + item);
4045 }
4046 final Workspace workspace = mWorkspace;
4047
4048 LauncherAppWidgetProviderInfo appWidgetInfo =
4049 LauncherModel.getProviderInfo(this, item.providerName, item.user);
4050
4051 if (!mIsSafeModeEnabled
4052 && ((item.restoreStatus & LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY) == 0)
4053 && ((item.restoreStatus & LauncherAppWidgetInfo.FLAG_ID_NOT_VALID) != 0)) {
4054 if (appWidgetInfo == null) {
4055 if (DEBUG_WIDGETS) {
4056 Log.d(TAG, "Removing restored widget: id=" + item.appWidgetId
4057 + " belongs to component " + item.providerName
4058 + ", as the povider is null");
4059 }
4060 LauncherModel.deleteItemFromDatabase(this, item);
4061 return;
4062 }
4063 // Note: This assumes that the id remap broadcast is received before this step.
4064 // If that is not the case, the id remap will be ignored and user may see the
4065 // click to setup view.
4066 PendingAddWidgetInfo pendingInfo = new PendingAddWidgetInfo(this, appWidgetInfo, null);
4067 pendingInfo.spanX = item.spanX;
4068 pendingInfo.spanY = item.spanY;
4069 pendingInfo.minSpanX = item.minSpanX;
4070 pendingInfo.minSpanY = item.minSpanY;
4071 Bundle options = null;
4072 WidgetHostViewLoader.getDefaultOptionsForWidget(this, pendingInfo);
4073
4074 int newWidgetId = mAppWidgetHost.allocateAppWidgetId();
4075 boolean success = mAppWidgetManager.bindAppWidgetIdIfAllowed(
4076 newWidgetId, appWidgetInfo, options);
4077
4078 // TODO consider showing a permission dialog when the widget is clicked.
4079 if (!success) {
4080 mAppWidgetHost.deleteAppWidgetId(newWidgetId);
4081 if (DEBUG_WIDGETS) {
4082 Log.d(TAG, "Removing restored widget: id=" + item.appWidgetId
4083 + " belongs to component " + item.providerName
4084 + ", as the launcher is unable to bing a new widget id");
4085 }
4086 LauncherModel.deleteItemFromDatabase(this, item);
4087 return;
4088 }
4089
4090 item.appWidgetId = newWidgetId;
4091
4092 // If the widget has a configure activity, it is still needs to set it up, otherwise
4093 // the widget is ready to go.
4094 item.restoreStatus = (appWidgetInfo.configure == null)
4095 ? LauncherAppWidgetInfo.RESTORE_COMPLETED
4096 : LauncherAppWidgetInfo.FLAG_UI_NOT_READY;
4097
4098 LauncherModel.updateItemInDatabase(this, item);
4099 }
4100
4101 if (!mIsSafeModeEnabled && item.restoreStatus == LauncherAppWidgetInfo.RESTORE_COMPLETED) {
4102 final int appWidgetId = item.appWidgetId;
4103 if (DEBUG_WIDGETS) {
4104 Log.d(TAG, "bindAppWidget: id=" + item.appWidgetId + " belongs to component "
4105 + appWidgetInfo.provider);
4106 }
4107
4108 item.hostView = mAppWidgetHost.createView(this, appWidgetId, appWidgetInfo);
4109 } else {
4110 appWidgetInfo = null;
4111 PendingAppWidgetHostView view = new PendingAppWidgetHostView(this, item,
4112 mIsSafeModeEnabled);
4113 view.updateIcon(mIconCache);
4114 item.hostView = view;
4115 item.hostView.updateAppWidget(null);
4116 item.hostView.setOnClickListener(this);
4117 }
4118
4119 item.hostView.setTag(item);
4120 item.onBindAppWidget(this);
4121
4122 workspace.addInScreen(item.hostView, item.container, item.screenId, item.cellX,
4123 item.cellY, item.spanX, item.spanY, false);
4124 if (!item.isCustomWidget()) {
4125 addWidgetToAutoAdvanceIfNeeded(item.hostView, appWidgetInfo);
4126 }
4127
4128 workspace.requestLayout();
4129
4130 if (DEBUG_WIDGETS) {
4131 Log.d(TAG, "bound widget id="+item.appWidgetId+" in "
4132 + (SystemClock.uptimeMillis()-start) + "ms");
4133 }
4134 }
4135
4136 /**
4137 * Restores a pending widget.
4138 *
4139 * @param appWidgetId The app widget id
4140 * @param cellInfo The position on screen where to create the widget.
4141 */
4142 private void completeRestoreAppWidget(final int appWidgetId) {
4143 LauncherAppWidgetHostView view = mWorkspace.getWidgetForAppWidgetId(appWidgetId);
4144 if ((view == null) || !(view instanceof PendingAppWidgetHostView)) {
4145 Log.e(TAG, "Widget update called, when the widget no longer exists.");
4146 return;
4147 }
4148
4149 LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) view.getTag();
4150 info.restoreStatus = LauncherAppWidgetInfo.RESTORE_COMPLETED;
4151
4152 mWorkspace.reinflateWidgetsIfNecessary();
4153 LauncherModel.updateItemInDatabase(this, info);
4154 }
4155
4156 public void onPageBoundSynchronously(int page) {
4157 mSynchronouslyBoundPages.add(page);
4158 }
4159
4160 /**
4161 * Callback saying that there aren't any more items to bind.
4162 *
4163 * Implementation of the method from LauncherModel.Callbacks.
4164 */
4165 public void finishBindingItems() {
4166 Runnable r = new Runnable() {
4167 public void run() {
4168 finishBindingItems();
4169 }
4170 };
4171 if (waitUntilResume(r)) {
4172 return;
4173 }
4174 if (mSavedState != null) {
4175 if (!mWorkspace.hasFocus()) {
4176 mWorkspace.getChildAt(mWorkspace.getCurrentPage()).requestFocus();
4177 }
4178 mSavedState = null;
4179 }
4180
4181 mWorkspace.restoreInstanceStateForRemainingPages();
4182
4183 setWorkspaceLoading(false);
4184 sendLoadingCompleteBroadcastIfNecessary();
4185
4186 // If we received the result of any pending adds while the loader was running (e.g. the
4187 // widget configuration forced an orientation change), process them now.
4188 if (sPendingAddItem != null) {
4189 final long screenId = completeAdd(sPendingAddItem);
4190
4191 // TODO: this moves the user to the page where the pending item was added. Ideally,
4192 // the screen would be guaranteed to exist after bind, and the page would be set through
4193 // the workspace restore process.
4194 mWorkspace.post(new Runnable() {
4195 @Override
4196 public void run() {
4197 mWorkspace.snapToScreenId(screenId);
4198 }
4199 });
4200 sPendingAddItem = null;
4201 }
4202
4203 InstallShortcutReceiver.disableAndFlushInstallQueue(this);
4204
4205 if (mLauncherCallbacks != null) {
4206 mLauncherCallbacks.finishBindingItems(false);
4207 }
4208 }
4209
4210 private void sendLoadingCompleteBroadcastIfNecessary() {
4211 if (!mSharedPrefs.getBoolean(FIRST_LOAD_COMPLETE, false)) {
4212 String permission =
4213 getResources().getString(R.string.receive_first_load_broadcast_permission);
4214 Intent intent = new Intent(ACTION_FIRST_LOAD_COMPLETE);
4215 sendBroadcast(intent, permission);
4216 SharedPreferences.Editor editor = mSharedPrefs.edit();
4217 editor.putBoolean(FIRST_LOAD_COMPLETE, true);
4218 editor.apply();
4219 }
4220 }
4221
4222 public boolean isAllAppsButtonRank(int rank) {
4223 if (mHotseat != null) {
4224 return mHotseat.isAllAppsButtonRank(rank);
4225 }
4226 return false;
4227 }
4228
4229 private boolean canRunNewAppsAnimation() {
4230 long diff = System.currentTimeMillis() - mDragController.getLastGestureUpTime();
4231 return diff > (NEW_APPS_ANIMATION_INACTIVE_TIMEOUT_SECONDS * 1000);
4232 }
4233
4234 private ValueAnimator createNewAppBounceAnimation(View v, int i) {
4235 ValueAnimator bounceAnim = LauncherAnimUtils.ofPropertyValuesHolder(v,
4236 PropertyValuesHolder.ofFloat("alpha", 1f),
4237 PropertyValuesHolder.ofFloat("scaleX", 1f),
4238 PropertyValuesHolder.ofFloat("scaleY", 1f));
4239 bounceAnim.setDuration(InstallShortcutReceiver.NEW_SHORTCUT_BOUNCE_DURATION);
4240 bounceAnim.setStartDelay(i * InstallShortcutReceiver.NEW_SHORTCUT_STAGGER_DELAY);
4241 bounceAnim.setInterpolator(new SmoothPagedView.OvershootInterpolator());
4242 return bounceAnim;
4243 }
4244
4245 public boolean useVerticalBarLayout() {
4246 return mDeviceProfile.isVerticalBarLayout();
4247 }
4248
4249 protected Rect getSearchBarBounds() {
4250 return mDeviceProfile.getSearchBarBounds(Utilities.isRtl(getResources()));
4251 }
4252
4253 public void bindSearchablesChanged() {
4254 if (mSearchDropTargetBar == null) {
4255 return;
4256 }
4257 if (mQsb != null) {
4258 mSearchDropTargetBar.removeView(mQsb);
4259 mQsb = null;
4260 }
4261 mSearchDropTargetBar.setQsbSearchBar(getOrCreateQsbBar());
4262 }
4263
4264 /**
4265 * A runnable that we can dequeue and re-enqueue when all applications are bound (to prevent
4266 * multiple calls to bind the same list.)
4267 */
4268 @Thunk ArrayList<AppInfo> mTmpAppsList;
4269 private Runnable mBindAllApplicationsRunnable = new Runnable() {
4270 public void run() {
4271 bindAllApplications(mTmpAppsList);
4272 mTmpAppsList = null;
4273 }
4274 };
4275
4276 /**
4277 * Add the icons for all apps.
4278 *
4279 * Implementation of the method from LauncherModel.Callbacks.
4280 */
4281 public void bindAllApplications(final ArrayList<AppInfo> apps) {
4282 if (waitUntilResume(mBindAllApplicationsRunnable, true)) {
4283 mTmpAppsList = apps;
4284 return;
4285 }
4286
4287 if (mAppsView != null) {
4288 mAppsView.setApps(apps);
4289 }
4290 if (mLauncherCallbacks != null) {
4291 mLauncherCallbacks.bindAllApplications(apps);
4292 }
4293 }
4294
4295 /**
4296 * A package was updated.
4297 *
4298 * Implementation of the method from LauncherModel.Callbacks.
4299 */
4300 public void bindAppsUpdated(final ArrayList<AppInfo> apps) {
4301 Runnable r = new Runnable() {
4302 public void run() {
4303 bindAppsUpdated(apps);
4304 }
4305 };
4306 if (waitUntilResume(r)) {
4307 return;
4308 }
4309
4310 if (mAppsView != null) {
4311 mAppsView.updateApps(apps);
4312 }
4313 }
4314
4315 @Override
4316 public void bindWidgetsRestored(final ArrayList<LauncherAppWidgetInfo> widgets) {
4317 Runnable r = new Runnable() {
4318 public void run() {
4319 bindWidgetsRestored(widgets);
4320 }
4321 };
4322 if (waitUntilResume(r)) {
4323 return;
4324 }
4325 mWorkspace.widgetsRestored(widgets);
4326 }
4327
4328 /**
4329 * Some shortcuts were updated in the background.
4330 *
4331 * Implementation of the method from LauncherModel.Callbacks.
4332 */
4333 @Override
4334 public void bindShortcutsChanged(final ArrayList<ShortcutInfo> updated,
4335 final ArrayList<ShortcutInfo> removed, final UserHandleCompat user) {
4336 Runnable r = new Runnable() {
4337 public void run() {
4338 bindShortcutsChanged(updated, removed, user);
4339 }
4340 };
4341 if (waitUntilResume(r)) {
4342 return;
4343 }
4344
4345 if (!updated.isEmpty()) {
4346 mWorkspace.updateShortcuts(updated);
4347 }
4348
4349 if (!removed.isEmpty()) {
4350 HashSet<ComponentName> removedComponents = new HashSet<ComponentName>();
4351 for (ShortcutInfo si : removed) {
4352 removedComponents.add(si.getTargetComponent());
4353 }
4354 mWorkspace.removeItemsByComponentName(removedComponents, user);
4355 // Notify the drag controller
4356 mDragController.onAppsRemoved(new ArrayList<String>(), removedComponents);
4357 }
4358 }
4359
4360 /**
4361 * Update the state of a package, typically related to install state.
4362 *
4363 * Implementation of the method from LauncherModel.Callbacks.
4364 */
4365 @Override
4366 public void bindRestoreItemsChange(final HashSet<ItemInfo> updates) {
4367 Runnable r = new Runnable() {
4368 public void run() {
4369 bindRestoreItemsChange(updates);
4370 }
4371 };
4372 if (waitUntilResume(r)) {
4373 return;
4374 }
4375
4376 mWorkspace.updateRestoreItems(updates);
4377 }
4378
4379 /**
4380 * A package was uninstalled. We take both the super set of packageNames
4381 * in addition to specific applications to remove, the reason being that
4382 * this can be called when a package is updated as well. In that scenario,
4383 * we only remove specific components from the workspace, where as
4384 * package-removal should clear all items by package name.
4385 *
4386 * @param reason if non-zero, the icons are not permanently removed, rather marked as disabled.
4387 * Implementation of the method from LauncherModel.Callbacks.
4388 */
4389 @Override
4390 public void bindComponentsRemoved(final ArrayList<String> packageNames,
4391 final ArrayList<AppInfo> appInfos, final UserHandleCompat user, final int reason) {
4392 Runnable r = new Runnable() {
4393 public void run() {
4394 bindComponentsRemoved(packageNames, appInfos, user, reason);
4395 }
4396 };
4397 if (waitUntilResume(r)) {
4398 return;
4399 }
4400
4401 if (reason == 0) {
4402 HashSet<ComponentName> removedComponents = new HashSet<ComponentName>();
4403 for (AppInfo info : appInfos) {
4404 removedComponents.add(info.componentName);
4405 }
4406 if (!packageNames.isEmpty()) {
4407 mWorkspace.removeItemsByPackageName(packageNames, user);
4408 }
4409 if (!removedComponents.isEmpty()) {
4410 mWorkspace.removeItemsByComponentName(removedComponents, user);
4411 }
4412 // Notify the drag controller
4413 mDragController.onAppsRemoved(packageNames, removedComponents);
4414
4415 } else {
4416 mWorkspace.disableShortcutsByPackageName(packageNames, user, reason);
4417 }
4418
4419 // Update AllApps
4420 if (mAppsView != null) {
4421 mAppsView.removeApps(appInfos);
4422 }
4423 }
4424
4425 private Runnable mBindPackagesUpdatedRunnable = new Runnable() {
4426 public void run() {
4427 bindAllPackages(mWidgetsModel);
4428 }
4429 };
4430
4431 @Override
4432 public void bindAllPackages(final WidgetsModel model) {
4433 if (waitUntilResume(mBindPackagesUpdatedRunnable, true)) {
4434 mWidgetsModel = model;
4435 return;
4436 }
4437
4438 if (mWidgetsView != null && model != null) {
4439 mWidgetsView.addWidgets(model);
4440 mWidgetsModel = null;
4441 }
4442 }
4443
4444 private int mapConfigurationOriActivityInfoOri(int configOri) {
4445 final Display d = getWindowManager().getDefaultDisplay();
4446 int naturalOri = Configuration.ORIENTATION_LANDSCAPE;
4447 switch (d.getRotation()) {
4448 case Surface.ROTATION_0:
4449 case Surface.ROTATION_180:
4450 // We are currently in the same basic orientation as the natural orientation
4451 naturalOri = configOri;
4452 break;
4453 case Surface.ROTATION_90:
4454 case Surface.ROTATION_270:
4455 // We are currently in the other basic orientation to the natural orientation
4456 naturalOri = (configOri == Configuration.ORIENTATION_LANDSCAPE) ?
4457 Configuration.ORIENTATION_PORTRAIT : Configuration.ORIENTATION_LANDSCAPE;
4458 break;
4459 }
4460
4461 int[] oriMap = {
4462 ActivityInfo.SCREEN_ORIENTATION_PORTRAIT,
4463 ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE,
4464 ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT,
4465 ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE
4466 };
4467 // Since the map starts at portrait, we need to offset if this device's natural orientation
4468 // is landscape.
4469 int indexOffset = 0;
4470 if (naturalOri == Configuration.ORIENTATION_LANDSCAPE) {
4471 indexOffset = 1;
4472 }
4473 return oriMap[(d.getRotation() + indexOffset) % 4];
4474 }
4475
4476 public void lockScreenOrientation() {
4477 if (Utilities.isRotationEnabled(this)) {
4478 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
4479 setRequestedOrientation(mapConfigurationOriActivityInfoOri(getResources()
4480 .getConfiguration().orientation));
4481 } else {
4482 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LOCKED);
4483 }
4484 }
4485 }
4486 public void unlockScreenOrientation(boolean immediate) {
4487 if (Utilities.isRotationEnabled(this)) {
4488 if (immediate) {
4489 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
4490 } else {
4491 mHandler.postDelayed(new Runnable() {
4492 public void run() {
4493 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
4494 }
4495 }, mRestoreScreenOrientationDelay);
4496 }
4497 }
4498 }
4499
4500 protected boolean isLauncherPreinstalled() {
4501 if (mLauncherCallbacks != null) {
4502 return mLauncherCallbacks.isLauncherPreinstalled();
4503 }
4504 PackageManager pm = getPackageManager();
4505 try {
4506 ApplicationInfo ai = pm.getApplicationInfo(getComponentName().getPackageName(), 0);
4507 if ((ai.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
4508 return true;
4509 } else {
4510 return false;
4511 }
4512 } catch (NameNotFoundException e) {
4513 e.printStackTrace();
4514 return false;
4515 }
4516 }
4517
4518 /**
4519 * This method indicates whether or not we should suggest default wallpaper dimensions
4520 * when our wallpaper cropper was not yet used to set a wallpaper.
4521 */
4522 protected boolean overrideWallpaperDimensions() {
4523 if (mLauncherCallbacks != null) {
4524 return mLauncherCallbacks.overrideWallpaperDimensions();
4525 }
4526 return true;
4527 }
4528
4529 /**
4530 * To be overridden by subclasses to indicate that there is an activity to launch
4531 * before showing the standard launcher experience.
4532 */
4533 protected boolean hasFirstRunActivity() {
4534 if (mLauncherCallbacks != null) {
4535 return mLauncherCallbacks.hasFirstRunActivity();
4536 }
4537 return false;
4538 }
4539
4540 /**
4541 * To be overridden by subclasses to launch any first run activity
4542 */
4543 protected Intent getFirstRunActivity() {
4544 if (mLauncherCallbacks != null) {
4545 return mLauncherCallbacks.getFirstRunActivity();
4546 }
4547 return null;
4548 }
4549
4550 /**
4551 * Returns whether the launcher callbacks overrides search in all apps.
4552 */
4553 @Thunk boolean isAllAppsSearchOverridden() {
4554 if (DISABLE_ALL_APPS_SEARCH_INTEGRATION) {
4555 return false;
4556 }
4557
4558 if (mLauncherCallbacks != null) {
4559 return mLauncherCallbacks.overrideAllAppsSearch();
4560 }
4561 return false;
4562 }
4563
4564 private boolean shouldRunFirstRunActivity() {
4565 return !ActivityManager.isRunningInTestHarness() &&
4566 !mSharedPrefs.getBoolean(FIRST_RUN_ACTIVITY_DISPLAYED, false);
4567 }
4568
4569 protected boolean hasRunFirstRunActivity() {
4570 return mSharedPrefs.getBoolean(FIRST_RUN_ACTIVITY_DISPLAYED, false);
4571 }
4572
4573 public boolean showFirstRunActivity() {
4574 if (shouldRunFirstRunActivity() &&
4575 hasFirstRunActivity()) {
4576 Intent firstRunIntent = getFirstRunActivity();
4577 if (firstRunIntent != null) {
4578 startActivity(firstRunIntent);
4579 markFirstRunActivityShown();
4580 return true;
4581 }
4582 }
4583 return false;
4584 }
4585
4586 private void markFirstRunActivityShown() {
4587 SharedPreferences.Editor editor = mSharedPrefs.edit();
4588 editor.putBoolean(FIRST_RUN_ACTIVITY_DISPLAYED, true);
4589 editor.apply();
4590 }
4591
4592 /**
4593 * To be overridden by subclasses to indicate that there is an in-activity full-screen intro
4594 * screen that must be displayed and dismissed.
4595 */
4596 protected boolean hasDismissableIntroScreen() {
4597 if (mLauncherCallbacks != null) {
4598 return mLauncherCallbacks.hasDismissableIntroScreen();
4599 }
4600 return false;
4601 }
4602
4603 /**
4604 * Full screen intro screen to be shown and dismissed before the launcher can be used.
4605 */
4606 protected View getIntroScreen() {
4607 if (mLauncherCallbacks != null) {
4608 return mLauncherCallbacks.getIntroScreen();
4609 }
4610 return null;
4611 }
4612
4613 /**
4614 * To be overriden by subclasses to indicate whether the in-activity intro screen has been
4615 * dismissed. This method is ignored if #hasDismissableIntroScreen returns false.
4616 */
4617 private boolean shouldShowIntroScreen() {
4618 return hasDismissableIntroScreen() &&
4619 !mSharedPrefs.getBoolean(INTRO_SCREEN_DISMISSED, false);
4620 }
4621
4622 protected void showIntroScreen() {
4623 View introScreen = getIntroScreen();
4624 changeWallpaperVisiblity(false);
4625 if (introScreen != null) {
4626 mDragLayer.showOverlayView(introScreen);
4627 }
4628 if (mLauncherOverlayContainer != null) {
4629 mLauncherOverlayContainer.setVisibility(View.INVISIBLE);
4630 }
4631 }
4632
4633 public void dismissIntroScreen() {
4634 markIntroScreenDismissed();
4635 if (showFirstRunActivity()) {
4636 // We delay hiding the intro view until the first run activity is showing. This
4637 // avoids a blip.
4638 mWorkspace.postDelayed(new Runnable() {
4639 @Override
4640 public void run() {
4641 mDragLayer.dismissOverlayView();
4642 if (mLauncherOverlayContainer != null) {
4643 mLauncherOverlayContainer.setVisibility(View.VISIBLE);
4644 }
4645 showFirstRunClings();
4646 }
4647 }, ACTIVITY_START_DELAY);
4648 } else {
4649 mDragLayer.dismissOverlayView();
4650 if (mLauncherOverlayContainer != null) {
4651 mLauncherOverlayContainer.setVisibility(View.VISIBLE);
4652 }
4653 showFirstRunClings();
4654 }
4655 changeWallpaperVisiblity(true);
4656 }
4657
4658 private void markIntroScreenDismissed() {
4659 SharedPreferences.Editor editor = mSharedPrefs.edit();
4660 editor.putBoolean(INTRO_SCREEN_DISMISSED, true);
4661 editor.apply();
4662 }
4663
4664 @Thunk void showFirstRunClings() {
4665 // The two first run cling paths are mutually exclusive, if the launcher is preinstalled
4666 // on the device, then we always show the first run cling experience (or if there is no
4667 // launcher2). Otherwise, we prompt the user upon started for migration
4668 LauncherClings launcherClings = new LauncherClings(this);
4669 if (launcherClings.shouldShowFirstRunOrMigrationClings()) {
4670 if (mModel.canMigrateFromOldLauncherDb(this)) {
4671 launcherClings.showMigrationCling();
4672 } else {
4673 launcherClings.showLongPressCling(true);
4674 }
4675 }
4676 }
4677
4678 void showWorkspaceSearchAndHotseat() {
4679 if (mWorkspace != null) mWorkspace.setAlpha(1f);
4680 if (mHotseat != null) mHotseat.setAlpha(1f);
4681 if (mPageIndicators != null) mPageIndicators.setAlpha(1f);
4682 if (mSearchDropTargetBar != null) mSearchDropTargetBar.showSearchBar(false);
4683 }
4684
4685 void hideWorkspaceSearchAndHotseat() {
4686 if (mWorkspace != null) mWorkspace.setAlpha(0f);
4687 if (mHotseat != null) mHotseat.setAlpha(0f);
4688 if (mPageIndicators != null) mPageIndicators.setAlpha(0f);
4689 if (mSearchDropTargetBar != null) mSearchDropTargetBar.hideSearchBar(false);
4690 }
4691
4692 public ItemInfo createAppDragInfo(Intent appLaunchIntent) {
4693 // Called from search suggestion, not supported in other profiles.
4694 final UserHandleCompat myUser = UserHandleCompat.myUserHandle();
4695 LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(this);
4696 LauncherActivityInfoCompat activityInfo = launcherApps.resolveActivity(appLaunchIntent,
4697 myUser);
4698 if (activityInfo == null) {
4699 return null;
4700 }
4701 return new AppInfo(this, activityInfo, myUser, mIconCache);
4702 }
4703
4704 public ItemInfo createShortcutDragInfo(Intent shortcutIntent, CharSequence caption,
4705 Bitmap icon) {
4706 // Called from search suggestion, not supported in other profiles.
4707 return createShortcutDragInfo(shortcutIntent, caption, icon,
4708 UserHandleCompat.myUserHandle());
4709 }
4710
4711 public ItemInfo createShortcutDragInfo(Intent shortcutIntent, CharSequence caption,
4712 Bitmap icon, UserHandleCompat user) {
4713 UserManagerCompat userManager = UserManagerCompat.getInstance(this);
4714 CharSequence contentDescription = userManager.getBadgedLabelForUser(caption, user);
4715 return new ShortcutInfo(shortcutIntent, caption, contentDescription, icon, user);
4716 }
4717
4718 protected void moveWorkspaceToDefaultScreen() {
4719 mWorkspace.moveToDefaultScreen(false);
4720 }
4721
4722 public void startDrag(View dragView, ItemInfo dragInfo, DragSource source) {
4723 dragView.setTag(dragInfo);
4724 mWorkspace.onExternalDragStartedWithItem(dragView);
4725 mWorkspace.beginExternalDragShared(dragView, source);
4726 }
4727
4728 @Override
4729 public void onPageSwitch(View newPage, int newPageIndex) {
4730 if (mLauncherCallbacks != null) {
4731 mLauncherCallbacks.onPageSwitch(newPage, newPageIndex);
4732 }
4733 }
4734
4735 /**
4736 * Prints out out state for debugging.
4737 */
4738 public void dumpState() {
4739 Log.d(TAG, "BEGIN launcher3 dump state for launcher " + this);
4740 Log.d(TAG, "mSavedState=" + mSavedState);
4741 Log.d(TAG, "mWorkspaceLoading=" + mWorkspaceLoading);
4742 Log.d(TAG, "mRestoring=" + mRestoring);
4743 Log.d(TAG, "mWaitingForResult=" + mWaitingForResult);
4744 Log.d(TAG, "mSavedInstanceState=" + mSavedInstanceState);
4745 Log.d(TAG, "sFolders.size=" + sFolders.size());
4746 mModel.dumpState();
4747 // TODO(hyunyoungs): add mWidgetsView.dumpState(); or mWidgetsModel.dumpState();
4748
4749 Log.d(TAG, "END launcher3 dump state");
4750 }
4751
4752 @Override
4753 public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
4754 super.dump(prefix, fd, writer, args);
4755 synchronized (sDumpLogs) {
4756 writer.println(" ");
4757 writer.println("Debug logs: ");
4758 for (int i = 0; i < sDumpLogs.size(); i++) {
4759 writer.println(" " + sDumpLogs.get(i));
4760 }
4761 }
4762 if (mLauncherCallbacks != null) {
4763 mLauncherCallbacks.dump(prefix, fd, writer, args);
4764 }
4765 }
4766
4767 public static void dumpDebugLogsToConsole() {
4768 if (DEBUG_DUMP_LOG) {
4769 synchronized (sDumpLogs) {
4770 Log.d(TAG, "");
4771 Log.d(TAG, "*********************");
4772 Log.d(TAG, "Launcher debug logs: ");
4773 for (int i = 0; i < sDumpLogs.size(); i++) {
4774 Log.d(TAG, " " + sDumpLogs.get(i));
4775 }
4776 Log.d(TAG, "*********************");
4777 Log.d(TAG, "");
4778 }
4779 }
4780 }
4781
4782 public static void addDumpLog(String tag, String log, boolean debugLog) {
4783 addDumpLog(tag, log, null, debugLog);
4784 }
4785
4786 public static void addDumpLog(String tag, String log, Exception e, boolean debugLog) {
4787 if (debugLog) {
4788 if (e != null) {
4789 Log.d(tag, log, e);
4790 } else {
4791 Log.d(tag, log);
4792 }
4793 }
4794 if (DEBUG_DUMP_LOG) {
4795 sDateStamp.setTime(System.currentTimeMillis());
4796 synchronized (sDumpLogs) {
4797 sDumpLogs.add(sDateFormat.format(sDateStamp) + ": " + tag + ", " + log
4798 + (e == null ? "" : (", Exception: " + e)));
4799 }
4800 }
4801 }
4802
4803 public static CustomAppWidget getCustomAppWidget(String name) {
4804 return sCustomAppWidgets.get(name);
4805 }
4806
4807 public static HashMap<String, CustomAppWidget> getCustomAppWidgets() {
4808 return sCustomAppWidgets;
4809 }
4810
4811 public void dumpLogsToLocalData() {
4812 if (DEBUG_DUMP_LOG) {
4813 new AsyncTask<Void, Void, Void>() {
4814 public Void doInBackground(Void ... args) {
4815 boolean success = false;
4816 sDateStamp.setTime(sRunStart);
4817 String FILENAME = sDateStamp.getMonth() + "-"
4818 + sDateStamp.getDay() + "_"
4819 + sDateStamp.getHours() + "-"
4820 + sDateStamp.getMinutes() + "_"
4821 + sDateStamp.getSeconds() + ".txt";
4822
4823 FileOutputStream fos = null;
4824 File outFile = null;
4825 try {
4826 outFile = new File(getFilesDir(), FILENAME);
4827 outFile.createNewFile();
4828 fos = new FileOutputStream(outFile);
4829 } catch (Exception e) {
4830 e.printStackTrace();
4831 }
4832 if (fos != null) {
4833 PrintWriter writer = new PrintWriter(fos);
4834
4835 writer.println(" ");
4836 writer.println("Debug logs: ");
4837 synchronized (sDumpLogs) {
4838 for (int i = 0; i < sDumpLogs.size(); i++) {
4839 writer.println(" " + sDumpLogs.get(i));
4840 }
4841 }
4842 writer.close();
4843 }
4844 try {
4845 if (fos != null) {
4846 fos.close();
4847 success = true;
4848 }
4849 } catch (IOException e) {
4850 e.printStackTrace();
4851 }
4852 return null;
4853 }
4854 }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void) null);
4855 }
4856 }
4857 }
4858
4859 interface DebugIntents {
4860 static final String DELETE_DATABASE = "com.android.launcher3.action.DELETE_DATABASE";
4861 static final String MIGRATE_DATABASE = "com.android.launcher3.action.MIGRATE_DATABASE";
4862 }
|
1 /*
2 * Copyright (C) 2008 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16 package com.android.launcher3;
17
18 import android.animation.Animator;
19 import android.animation.AnimatorListenerAdapter;
20 import android.animation.AnimatorSet;
21 import android.animation.ObjectAnimator;
22 import android.animation.PropertyValuesHolder;
23 import android.animation.ValueAnimator;
24 import android.annotation.SuppressLint;
25 import android.annotation.TargetApi;
26 import android.app.Activity;
27 import android.app.ActivityManager;
28 import android.app.ActivityOptions;
29 import android.app.AlertDialog;
30 import android.app.SearchManager;
31 import android.appwidget.AppWidgetHostView;
32 import android.appwidget.AppWidgetManager;
33 import android.appwidget.AppWidgetProviderInfo;
34 import android.content.ActivityNotFoundException;
35 import android.content.BroadcastReceiver;
36 import android.content.ComponentCallbacks2;
37 import android.content.ComponentName;
38 import android.content.ContentResolver;
39 import android.content.Context;
40 import android.content.DialogInterface;
41 import android.content.Intent;
42 import android.content.IntentFilter;
43 import android.content.IntentSender;
44 import android.content.SharedPreferences;
45 import android.content.pm.ActivityInfo;
46 import android.content.pm.ApplicationInfo;
47 import android.content.pm.PackageManager.NameNotFoundException;
48 import android.content.pm.PackageManager;
49 import android.content.res.Configuration;
50 import android.database.ContentObserver;
51 import android.database.sqlite.SQLiteDatabase;
52 import android.graphics.Bitmap;
53 import android.graphics.Canvas;
54 import android.graphics.Color;
55 import android.graphics.PorterDuff;
56 import android.graphics.Rect;
57 import android.graphics.drawable.ColorDrawable;
58 import android.graphics.drawable.Drawable;
59 import android.net.Uri;
60 import android.os.AsyncTask;
61 import android.os.Build;
62 import android.os.Bundle;
63 import android.os.Environment;
64 import android.os.Handler;
65 import android.os.Message;
66 import android.os.StrictMode;
67 import android.os.SystemClock;
68 import android.text.Selection;
69 import android.text.SpannableStringBuilder;
70 import android.text.TextUtils;
71 import android.text.method.TextKeyListener;
72 import android.util.Log;
73 import android.view.Display;
74 import android.view.Gravity;
75 import android.view.HapticFeedbackConstants;
76 import android.view.KeyEvent;
77 import android.view.LayoutInflater;
78 import android.view.Menu;
79 import android.view.MotionEvent;
80 import android.view.Surface;
81 import android.view.View.OnClickListener;
82 import android.view.View.OnLongClickListener;
83 import android.view.View;
84 import android.view.ViewGroup;
85 import android.view.ViewStub;
86 import android.view.ViewTreeObserver;
87 import android.view.Window;
88 import android.view.WindowManager;
89 import android.view.accessibility.AccessibilityEvent;
90 import android.view.inputmethod.InputMethodManager;
91 import android.widget.Advanceable;
92 import android.widget.FrameLayout;
93 import android.widget.ImageView;
94 import android.widget.TextView;
95 import android.widget.Toast;
96 import com.android.launcher3.DropTarget.DragObject;
97 import com.android.launcher3.PagedView.PageSwitchListener;
98 import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
99 import com.android.launcher3.allapps.AllAppsContainerView;
100 import com.android.launcher3.compat.AppWidgetManagerCompat;
101 import com.android.launcher3.compat.LauncherActivityInfoCompat;
102 import com.android.launcher3.compat.LauncherAppsCompat;
103 import com.android.launcher3.compat.UserHandleCompat;
104 import com.android.launcher3.compat.UserManagerCompat;
105 import com.android.launcher3.model.WidgetsModel;
106 import com.android.launcher3.util.LongArrayMap;
107 import com.android.launcher3.util.Thunk;
108 import com.android.launcher3.widget.PendingAddWidgetInfo;
109 import com.android.launcher3.widget.WidgetHostViewLoader;
110 import com.android.launcher3.widget.WidgetsContainerView;
111 import java.io.DataInputStream;
112 import java.io.DataOutputStream;
113 import java.io.File;
114 import java.io.FileDescriptor;
115 import java.io.FileNotFoundException;
116 import java.io.FileOutputStream;
117 import java.io.IOException;
118 import java.io.PrintWriter;
119 import java.lang.reflect.InvocationTargetException;
120 import java.lang.reflect.Method;
121 import java.text.DateFormat;
122 import java.util.ArrayList;
123 import java.util.Collection;
124 import java.util.Date;
125 import java.util.HashMap;
126 import java.util.HashSet;
127 import java.util.List;
128 import java.util.concurrent.atomic.AtomicInteger;
129
130
131 interface DebugIntents {
132 public static final String DELETE_DATABASE = "com.android.launcher3.action.DELETE_DATABASE";
133
134 public static final String MIGRATE_DATABASE = "com.android.launcher3.action.MIGRATE_DATABASE";
135 }
136
137 /**
138 * Default launcher application.
139 */
140 public class Launcher extends Activity implements View.OnClickListener , OnLongClickListener , LauncherMo🔵
141 static final String TAG = "Launcher";
142
143 static final boolean LOGD = false;
144
145 // Temporary flag
146 // Temporary flag
147 static final boolean DISABLE_ALL_APPS_SEARCH_INTEGRATION = true;
148
149 static final boolean PROFILE_STARTUP = false;
150
151 static final boolean DEBUG_WIDGETS = true;
152
153 static final boolean DEBUG_STRICT_MODE = false;
154
155 static final boolean DEBUG_RESUME_TIME = false;
156
157 static final boolean DEBUG_DUMP_LOG = false;
158
159 // allow DebugIntents to run
160 static final boolean ENABLE_DEBUG_INTENTS = false; // allow DebugIntents to run
161
162 private static final int REQUEST_CREATE_SHORTCUT = 1;
163
164 private static final int REQUEST_CREATE_APPWIDGET = 5;
165
166 private static final int REQUEST_PICK_APPWIDGET = 9;
167
168 private static final int REQUEST_PICK_WALLPAPER = 10;
169
170 private static final int REQUEST_BIND_APPWIDGET = 11;
171
172 private static final int REQUEST_RECONFIGURE_APPWIDGET = 12;
173
174 private static final int WORKSPACE_BACKGROUND_GRADIENT = 0;
175
176 private static final int WORKSPACE_BACKGROUND_TRANSPARENT = 1;
177
178 private static final int WORKSPACE_BACKGROUND_BLACK = 2;
179
180 /**
181 * IntentStarter uses request codes starting with this. This must be greater than all activity
182 * request codes used internally.
183 */
184 protected static final int REQUEST_LAST = 100;
185
186 static final String EXTRA_SHORTCUT_DUPLICATE = "duplicate";
187
188 static final int SCREEN_COUNT = 5;
189
190 // To turn on these properties, type
191 // adb shell setprop log.tag.PROPERTY_NAME [VERBOSE | SUPPRESS]
192 static final String DUMP_STATE_PROPERTY = "launcher_dump_state";
193
194 // The Intent extra that defines whether to ignore the launch animation
195 // The Intent extra that defines whether to ignore the launch animation
196 static final String INTENT_EXTRA_IGNORE_LAUNCH_ANIMATION =
197 "com.android.launcher3.intent.extra.shortcut.INGORE_LAUNCH_ANIMATION";
198
199 // Type: int
200 // Type: int
201 private static final String RUNTIME_STATE_CURRENT_SCREEN = "launcher.current_screen";
202
203 // Type: int
204 // Type: int
205 private static final String RUNTIME_STATE = "launcher.state";
206
207 // Type: int
208 // Type: int
209 private static final String RUNTIME_STATE_PENDING_ADD_CONTAINER = "launcher.add_container";
210
211 // Type: int
212 // Type: int
213 private static final String RUNTIME_STATE_PENDING_ADD_SCREEN = "launcher.add_screen";
214
215 // Type: int
216 // Type: int
217 private static final String RUNTIME_STATE_PENDING_ADD_CELL_X = "launcher.add_cell_x";
218
219 // Type: int
220 // Type: int
221 private static final String RUNTIME_STATE_PENDING_ADD_CELL_Y = "launcher.add_cell_y";
222
223 // Type: int
224 // Type: int
225 private static final String RUNTIME_STATE_PENDING_ADD_SPAN_X = "launcher.add_span_x";
226
227 // Type: int
228 // Type: int
229 private static final String RUNTIME_STATE_PENDING_ADD_SPAN_Y = "launcher.add_span_y";
230
231 // Type: parcelable
232 // Type: parcelable
233 private static final String RUNTIME_STATE_PENDING_ADD_WIDGET_INFO = "launcher.add_widget_info";
234
235 // Type: parcelable
236 // Type: parcelable
237 private static final String RUNTIME_STATE_PENDING_ADD_WIDGET_ID = "launcher.add_widget_id";
238
239 // Type: int[]
240 // Type: int[]
241 private static final String RUNTIME_STATE_VIEW_IDS = "launcher.view_ids";
242
243 static final String INTRO_SCREEN_DISMISSED = "launcher.intro_screen_dismissed";
244
245 static final String FIRST_RUN_ACTIVITY_DISPLAYED = "launcher.first_run_activity_displayed";
246
247 static final String FIRST_LOAD_COMPLETE = "launcher.first_load_complete";
248
249 static final String ACTION_FIRST_LOAD_COMPLETE =
250 "com.android.launcher3.action.FIRST_LOAD_COMPLETE";
251
252 public static final String SHOW_WEIGHT_WATCHER = "debug.show_mem";
253
254 public static final boolean SHOW_WEIGHT_WATCHER_DEFAULT = false;
255
256 private static final String QSB_WIDGET_ID = "qsb_widget_id";
257
258 private static final String QSB_WIDGET_PROVIDER = "qsb_widget_provider";
259
260 public static final String USER_HAS_MIGRATED = "launcher.user_migrated_from_old_data";
261
262 /**
263 * The different states that Launcher can be in.
264 */
265 enum State {
266
267 NONE,
268 WORKSPACE,
269 APPS,
270 APPS_SPRING_LOADED,
271 WIDGETS,
272 WIDGETS_SPRING_LOADED;}
273
274 @Thunk
275 State mState = State.WORKSPACE;
276
277 @Thunk LauncherStateTransitionAnimation mStateTransitionAnimation;
278
279 private boolean mIsSafeModeEnabled;
280
281 LauncherOverlayCallbacks mLauncherOverlayCallbacks = new LauncherOverlayCallbacksImpl();
282
283 LauncherOverlay mLauncherOverlay;
284
285 InsettableFrameLayout mLauncherOverlayContainer;
286
287 static final int APPWIDGET_HOST_ID = 1024;
288
289 public static final int EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT = 300;
290
291 private static final int ON_ACTIVITY_RESULT_ANIMATION_DELAY = 500;
292
293 private static final int ACTIVITY_START_DELAY = 1000;
294
295 private HashMap<Integer, Integer> mItemIdToViewId = new HashMap<Integer, Integer>();
296
297 private static final AtomicInteger sNextGeneratedId = new AtomicInteger(1);
298
299 // How long to wait before the new-shortcut animation automatically pans the workspace
300 // How long to wait before the new-shortcut animation automatically pans the workspace
301 private static int NEW_APPS_PAGE_MOVE_DELAY = 500;
302
303 private static int NEW_APPS_ANIMATION_INACTIVE_TIMEOUT_SECONDS = 5;
304
305 @Thunk static int NEW_APPS_ANIMATION_DELAY = 500;
306
307 private final BroadcastReceiver mCloseSystemDialogsReceiver
308 = new CloseSystemDialogsIntentReceiver();
309
310 private final ContentObserver mWidgetObserver = new AppWidgetResetObserver();
311
312 private LayoutInflater mInflater;
313
314 @Thunk Workspace mWorkspace;
315
316 private View mLauncherView;
317
318 private View mPageIndicators;
319
320 @Thunk DragLayer mDragLayer;
321
322 private DragController mDragController;
323
324 private View mWeightWatcher;
325
326 private AppWidgetManagerCompat mAppWidgetManager;
327
328 private LauncherAppWidgetHost mAppWidgetHost;
329
330 @Thunk ItemInfo mPendingAddInfo = new ItemInfo();
331
332 private LauncherAppWidgetProviderInfo mPendingAddWidgetInfo;
333
334 private int mPendingAddWidgetId = -1;
335
336 private int[] mTmpAddItemCellCoordinates = new int[2];
337
338 private Hotseat mHotseat;
339
340 private ViewGroup mOverviewPanel;
341
342 private View mAllAppsButton;
343
344 private SearchDropTargetBar mSearchDropTargetBar;
345
346 // Main container view for the all apps screen.
347 // Main container view for the all apps screen.
348 @Thunk AllAppsContainerView mAppsView;
349
350 // Main container view and the model for the widget tray screen.
351 // Main container view and the model for the widget tray screen.
352 @Thunk WidgetsContainerView mWidgetsView;
353
354 @Thunk WidgetsModel mWidgetsModel;
355
356 private boolean mAutoAdvanceRunning = false;
357
358 private AppWidgetHostView mQsb;
359
360 private Bundle mSavedState;
361
362 // We set the state in both onCreate and then onNewIntent in some cases, which causes both
363 // scroll issues (because the workspace may not have been measured yet) and extra work.
364 // Instead, just save the state that we need to restore Launcher to, and commit it in onResume.
365 private State mOnResumeState = State.NONE;
366
367 private SpannableStringBuilder mDefaultKeySsb = null;
368
369 @Thunk boolean mWorkspaceLoading = true;
370
371 private boolean mPaused = true;
372
373 private boolean mRestoring;
374
375 private boolean mWaitingForResult;
376
377 private boolean mOnResumeNeedsLoad;
378
379 private ArrayList<Runnable> mBindOnResumeCallbacks = new ArrayList<Runnable>();
380
381 private ArrayList<Runnable> mOnResumeCallbacks = new ArrayList<Runnable>();
382
383 private Bundle mSavedInstanceState;
384
385 private LauncherModel mModel;
386
387 private IconCache mIconCache;
388
389 @Thunk boolean mUserPresent = true;
390
391 private boolean mVisible = false;
392
393 private boolean mHasFocus = false;
394
395 private boolean mAttached = false;
396
397 @Thunk static LocaleConfiguration sLocaleConfiguration = null;
398
399 private static LongArrayMap<FolderInfo> sFolders = new LongArrayMap<>();
400
401 private View.OnTouchListener mHapticFeedbackTouchListener;
402
403 // Related to the auto-advancing of widgets
404 // Related to the auto-advancing of widgets
405 private final int ADVANCE_MSG = 1;
406
407 private final int mAdvanceInterval = 20000;
408
409 private final int mAdvanceStagger = 250;
410
411 private long mAutoAdvanceSentTime;
412
413 private long mAutoAdvanceTimeLeft = -1;
414
415 @Thunk
416 HashMap<View, AppWidgetProviderInfo> mWidgetsToAdvance = new HashMap<View, AppWidgetProviderInfo>();
417
418 // Determines how long to wait after a rotation before restoring the screen orientation to
419 // match the sensor state.
420 // Determines how long to wait after a rotation before restoring the screen orientation to
421 // match the sensor state.
422 private final int mRestoreScreenOrientationDelay = 500;
423
424 @Thunk Drawable mWorkspaceBackgroundDrawable;
425
426 private final ArrayList<Integer> mSynchronouslyBoundPages = new ArrayList<Integer>();
427
428 private static final boolean DISABLE_SYNCHRONOUS_BINDING_CURRENT_PAGE = false;
429
430 static final ArrayList<String> sDumpLogs = new ArrayList<String>();
431
432 static Date sDateStamp = new Date();
433
434 static DateFormat sDateFormat = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT);
435
436 static long sRunStart = System.currentTimeMillis();
437
438 static final String CORRUPTION_EMAIL_SENT_KEY = "corruptionEmailSent";
439
440 // We only want to get the SharedPreferences once since it does an FS stat each time we get
441 // it from the context.
442 // We only want to get the SharedPreferences once since it does an FS stat each time we get
443 // it from the context.
444 private SharedPreferences mSharedPrefs;
445
446 // Holds the page that we need to animate to, and the icon views that we need to animate up
447 // when we scroll to that page on resume.
448 @Thunk
449 ImageView mFolderIconImageView;
450
451 private Bitmap mFolderIconBitmap;
452
453 private Canvas mFolderIconCanvas;
454
455 private Rect mRectForFolderAnimation = new Rect();
456
457 private DeviceProfile mDeviceProfile;
458
459 // This is set to the view that launched the activity that navigated the user away from
460 // launcher. Since there is no callback for when the activity has finished launching, enable
461 // the press state and keep this reference to reset the press state when we return to launcher.
462 private BubbleTextView mWaitingForResume;
463
464 protected static HashMap<String, CustomAppWidget> sCustomAppWidgets = new HashMap<String, CustomAppWi🔵
465
466 private static final boolean ENABLE_CUSTOM_WIDGET_TEST = false;
467
468 static {
469 if (ENABLE_CUSTOM_WIDGET_TEST) {
470 sCustomAppWidgets.put(DummyWidget.class.getName(), new DummyWidget());
471 }
472 }
473
474 // TODO: remove this field and call method directly when Launcher3 can depend on M APIs
475 // TODO: remove this field and call method directly when Launcher3 can depend on M APIs
476 private static Method sClipRevealMethod = null;
477
478 static {
479 Class<?> activityOptionsClass = ActivityOptions.class;
480 try {
481 sClipRevealMethod = activityOptionsClass.getDeclaredMethod("makeClipRevealAnimation", View.cl🔵
482 } catch (java.lang.Exception e) {
483 // Earlier version
484 }
485 }
486
487 @Thunk
488 Runnable mBuildLayersRunnable = new Runnable() {
489 public void run() {
490 if (mWorkspace != null) {
491 mWorkspace.buildPageHardwareLayers();
492 }
493 }
494 };
495
496 private static PendingAddArguments sPendingAddItem;
497
498 @Thunk
499 static class PendingAddArguments {
500 int requestCode;
501
502 Intent intent;
503
504 long container;
505
506 long screenId;
507
508 int cellX;
509
510 int cellY;
511
512 int appWidgetId;
513 }
514
515 private Stats mStats;
516
517 FocusIndicatorView mFocusHandler;
518
519 @Override
520 protected void onCreate(Bundle savedInstanceState) {
521 if (DEBUG_STRICT_MODE) {
522 StrictMode.setThreadPolicy(// or .detectAll() for all detectable problems
523 new StrictMode.ThreadPolicy.Builder().detectDiskReads().detectDiskWrites().detectNetwork().pe🔵
524 StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder().detectLeakedSqlLiteObjects().detectL🔵
525 }
526 if (mLauncherCallbacks != null) {
527 mLauncherCallbacks.preOnCreate();
528 }
529 super.onCreate(savedInstanceState);
530 LauncherAppState.setApplicationContext(getApplicationContext());
531 LauncherAppState app = LauncherAppState.getInstance();
532 LauncherAppState.getLauncherProvider().setLauncherProviderChangeListener(this);
533 // Load configuration-specific DeviceProfile
534 mDeviceProfile = (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LAND🔵
535 // TODO: Move this to icon cache.
536 Utilities.setIconSize(mDeviceProfile.iconSizePx);
537 // the LauncherApplication should call this, but in case of Instrumentation it might not be prese🔵
538 mSharedPrefs = getSharedPreferences(LauncherAppState.getSharedPreferencesKey(), Context.MODE_PRIV🔵
539 mIsSafeModeEnabled = getPackageManager().isSafeMode();
540 mModel = app.setLauncher(this);
541 mIconCache = app.getIconCache();
542 mDragController = new DragController(this);
543 mInflater = getLayoutInflater();
544 mStateTransitionAnimation = new LauncherStateTransitionAnimation(this, this);
545 mStats = new Stats(this);
546 mAppWidgetManager = AppWidgetManagerCompat.getInstance(this);
547 mAppWidgetHost = new LauncherAppWidgetHost(this, APPWIDGET_HOST_ID);
548 mAppWidgetHost.startListening();
549 // If we are getting an onCreate, we can actually preempt onResume and unset mPaused here,
550 // this also ensures that any synchronous binding below doesn't re-trigger another
551 // LauncherModel load.
552 mPaused = false;
553 if (PROFILE_STARTUP) {
554 android.os.Debug.startMethodTracing(Environment.getExternalStorageDirectory() + "/launcher");
555 }
556 checkForLocaleChange();
557 setContentView(R.layout.launcher);
558 setupViews();
559 mDeviceProfile.layout(this);
560 registerContentObservers();
561 lockAllApps();
562 mSavedState = savedInstanceState;
563 restoreState(mSavedState);
564 if (PROFILE_STARTUP) {
565 android.os.Debug.stopMethodTracing();
566 }
567 if (!mRestoring) {
568 if (DISABLE_SYNCHRONOUS_BINDING_CURRENT_PAGE) {
569 // If the user leaves launcher, then we should just load items asynchronously when
570 // they return.
571 mModel.startLoader(PagedView.INVALID_RESTORE_PAGE);
572 } else {
573 // We only load the page synchronously if the user rotates (or triggers a
574 // configuration change) while launcher is in the foreground
575 mModel.startLoader(mWorkspace.getRestorePage());
576 }
577 }
578 // For handling default keys
579 mDefaultKeySsb = new SpannableStringBuilder();
580 Selection.setSelection(mDefaultKeySsb, 0);
581 IntentFilter filter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
582 registerReceiver(mCloseSystemDialogsReceiver, filter);
583 // On large interfaces, we want the screen to auto-rotate based on the current orientation
584 unlockScreenOrientation(true);
585 if (mLauncherCallbacks != null) {
586 mLauncherCallbacks.onCreate(savedInstanceState);
587 if (mLauncherCallbacks.hasLauncherOverlay()) {
588 ViewStub stub = ((ViewStub) (findViewById(R.id.launcher_overlay_stub)));
589 mLauncherOverlayContainer = ((InsettableFrameLayout) (stub.inflate()));
590 mLauncherOverlay = mLauncherCallbacks.setLauncherOverlayView(mLauncherOverlayContainer, m🔵
591 mWorkspace.setLauncherOverlay(mLauncherOverlay);
592 }
593 }
594 if (shouldShowIntroScreen()) {
595 showIntroScreen();
596 } else {
597 showFirstRunActivity();
598 showFirstRunClings();
599 }
600 }
601
602 private LauncherCallbacks mLauncherCallbacks;
603
604 public void onPostCreate(Bundle savedInstanceState) {
605 super.onPostCreate(savedInstanceState);
606 if (mLauncherCallbacks != null) {
607 mLauncherCallbacks.onPostCreate(savedInstanceState);
608 }
609 }
610
611 public boolean setLauncherCallbacks(LauncherCallbacks callbacks) {
612 mLauncherCallbacks = callbacks;
613 mLauncherCallbacks.setLauncherAppsCallback(new Launcher.LauncherAppsCallbacks() {
614 @Override
615 public void onAllAppsBoundsChanged(Rect bounds) {
616 if (LOGD) {
617 Log.d(TAG, "onAllAppsBoundsChanged(Rect): " + bounds);
618 }
619 mAppsView.setFixedBounds(bounds);
620 mWidgetsView.setFixedBounds(bounds);
621 }
622
623 @Override
624 public void dismissAllApps() {
625 if (!DISABLE_ALL_APPS_SEARCH_INTEGRATION) {
626 // Dismiss All Apps if we aren't already paused/invisible
627 if (!mPaused) {
628 /* onCompleteRunnable */
629 /* notifyLauncherCallbacks */
630 showWorkspace(WorkspaceStateTransitionAnimation.SCROLL_TO_CURRENT_PAGE, true, nul🔵
631 }
632 }
633 }
634 });
635 return true;
636 }
637
638 @Override
639 public void onLauncherProviderChange() {
640 if (mLauncherCallbacks != null) {
641 mLauncherCallbacks.onLauncherProviderChange();
642 }
643 }
644
645 /** To be overridden by subclasses to hint to Launcher that we have custom content */
646 protected boolean hasCustomContentToLeft() {
647 if (mLauncherCallbacks != null) {
648 return mLauncherCallbacks.hasCustomContentToLeft();
649 }
650 return false;
651 }
652
653 /**
654 * To be overridden by subclasses to populate the custom content container and call
655 * {@link #addToCustomContentPage}. This will only be invoked if
656 * {@link #hasCustomContentToLeft()} is {@code true}.
657 */
658 protected void populateCustomContentContainer() {
659 if (mLauncherCallbacks != null) {
660 mLauncherCallbacks.populateCustomContentContainer();
661 }
662 }
663
664 /**
665 * Invoked by subclasses to signal a change to the {@link #addCustomContentToLeft} value to
666 * ensure the custom content page is added or removed if necessary.
667 */
668 protected void invalidateHasCustomContentToLeft() {
669 if (mWorkspace == null || mWorkspace.getScreenOrder().isEmpty()) {
670 // Not bound yet, wait for bindScreens to be called.
671 return;
672 }
673
674 if (!mWorkspace.hasCustomContent() && hasCustomContentToLeft()) {
675 // Create the custom content page and call the subclass to populate it.
676 mWorkspace.createCustomContentContainer();
677 populateCustomContentContainer();
678 } else if (mWorkspace.hasCustomContent() && !hasCustomContentToLeft()) {
679 mWorkspace.removeCustomContentPage();
680 }
681 }
682
683 @Thunk
684 void checkForLocaleChange() {
685 if (sLocaleConfiguration == null) {
686 new AsyncTask<Void, Void, LocaleConfiguration>() {
687 @Override
688 protected LocaleConfiguration doInBackground(Void... unused) {
689 LocaleConfiguration localeConfiguration = new LocaleConfiguration();
690 readConfiguration(Launcher.this, localeConfiguration);
691 return localeConfiguration;
692 }
693
694 @Override
695 protected void onPostExecute(LocaleConfiguration result) {
696 sLocaleConfiguration = result;
697 checkForLocaleChange();// recursive, but now with a locale configuration
698
699 }
700 }.execute();
701 return;
702 }
703 final Configuration configuration = getResources().getConfiguration();
704 final String previousLocale = Launcher.sLocaleConfiguration.locale;
705 final String locale = configuration.locale.toString();
706 final int previousMcc = Launcher.sLocaleConfiguration.mcc;
707 final int mcc = configuration.mcc;
708 final int previousMnc = Launcher.sLocaleConfiguration.mnc;
709 final int mnc = configuration.mnc;
710 boolean localeChanged = ((!locale.equals(previousLocale)) || (mcc != previousMcc)) || (mnc != pre🔵
711 if (localeChanged) {
712 Launcher.sLocaleConfiguration.locale = locale;
713 Launcher.sLocaleConfiguration.mcc = mcc;
714 Launcher.sLocaleConfiguration.mnc = mnc;
715 mIconCache.flush();
716 final LocaleConfiguration localeConfiguration = sLocaleConfiguration;
717 new AsyncTask<Void, Void, Void>() {
718 public Void doInBackground(Void... args) {
719 writeConfiguration(Launcher.this, localeConfiguration);
720 return null;
721 }
722 }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, ((Void) (null)));
723 }
724 }
725
726 @Thunk
727 static class LocaleConfiguration {
728 public String locale;
729
730 public int mcc = -1;
731
732 public int mnc = -1;
733 }
734
735 @Thunk
736 static void readConfiguration(Context context, LocaleConfiguration configuration) {
737 DataInputStream in = null;
738 try {
739 in = new DataInputStream(context.openFileInput(LauncherFiles.LAUNCHER_PREFERENCES));
740 configuration.locale = in.readUTF();
741 configuration.mcc = in.readInt();
742 configuration.mnc = in.readInt();
743 } catch (FileNotFoundException e) {
744 // Ignore
745 } catch (IOException e) {
746 // Ignore
747 } finally {
748 if (in != null) {
749 try {
750 in.close();
751 } catch (IOException e) {
752 // Ignore
753 }
754 }
755 }
756 }
757
758 @Thunk
759 static void writeConfiguration(Context context, LocaleConfiguration configuration) {
760 DataOutputStream out = null;
761 try {
762 out = new DataOutputStream(context.openFileOutput(LauncherFiles.LAUNCHER_PREFERENCES, MODE_PR🔵
763 out.writeUTF(configuration.locale);
764 out.writeInt(configuration.mcc);
765 out.writeInt(configuration.mnc);
766 out.flush();
767 } catch (FileNotFoundException e) {
768 // Ignore
769 } catch (IOException e) {
770 // noinspection ResultOfMethodCallIgnored
771 context.getFileStreamPath(LauncherFiles.LAUNCHER_PREFERENCES).delete();
772 } finally {
773 if (out != null) {
774 try {
775 out.close();
776 } catch (IOException e) {
777 // Ignore
778 }
779 }
780 }
781 }
782
783 public Stats getStats() {
784 return mStats;
785 }
786
787 public LayoutInflater getInflater() {
788 return mInflater;
789 }
790
791 public boolean isDraggingEnabled() {
792 // We prevent dragging when we are loading the workspace as it is possible to pick up a view
793 // that is subsequently removed from the workspace in startBinding().
794 return !mModel.isLoadingWorkspace();
795 }
796
797 @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
798 public static int generateViewId() {
799 if (Build.VERSION.SDK_INT >= 17) {
800 return View.generateViewId();
801 } else {
802 // View.generateViewId() is not available. The following fallback logic is a copy
803 // of its implementation.
804 for (;;) {
805 final int result = sNextGeneratedId.get();
806 // aapt-generated IDs have the high byte nonzero; clamp to the range under that.
807 int newValue = result + 1;
808 if (newValue > 0x00FFFFFF) newValue = 1; // Roll over to 1, not 0.
809 if (sNextGeneratedId.compareAndSet(result, newValue)) {
810 return result;
811 }
812 }
813 }
814 }
815
816 public int getViewIdForItem(ItemInfo info) {
817 // This cast is safe given the > 2B range for int.
818 int itemId = (int) info.id;
819 if (mItemIdToViewId.containsKey(itemId)) {
820 return mItemIdToViewId.get(itemId);
821 }
822 int viewId = generateViewId();
823 mItemIdToViewId.put(itemId, viewId);
824 return viewId;
825 }
826
827 /**
828 * Returns whether we should delay spring loaded mode -- for shortcuts and widgets that have
829 * a configuration step, this allows the proper animations to run after other transitions.
830 */
831 private long completeAdd(PendingAddArguments args) {
832 long screenId = args.screenId;
833 if (args.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
834 // When the screen id represents an actual screen (as opposed to a rank) we make sure
835 // that the drop page actually exists.
836 screenId = ensurePendingDropLayoutExists(args.screenId);
837 }
838
839 switch (args.requestCode) {
840 case REQUEST_CREATE_SHORTCUT:
841 completeAddShortcut(args.intent, args.container, screenId, args.cellX,
842 args.cellY);
843 break;
844 case REQUEST_CREATE_APPWIDGET:
845 completeAddAppWidget(args.appWidgetId, args.container, screenId, null, null);
846 break;
847 case REQUEST_RECONFIGURE_APPWIDGET:
848 completeRestoreAppWidget(args.appWidgetId);
849 break;
850 }
851 // Before adding this resetAddInfo(), after a shortcut was added to a workspace screen,
852 // if you turned the screen off and then back while in All Apps, Launcher would not
853 // return to the workspace. Clearing mAddInfo.container here fixes this issue
854 resetAddInfo();
855 return screenId;
856 }
857
858 private void handleActivityResult(final int requestCode, final int resultCode, final Intent data) {
859 // Reset the startActivity waiting flag
860 setWaitingForResult(false);
861 final int pendingAddWidgetId = mPendingAddWidgetId;
862 mPendingAddWidgetId = -1;
863 Runnable exitSpringLoaded = new Runnable() {
864 @Override
865 public void run() {
866 exitSpringLoadedDragModeDelayed(resultCode != RESULT_CANCELED, EXIT_SPRINGLOADED_MODE_SHO🔵
867 }
868 };
869 if (requestCode == REQUEST_BIND_APPWIDGET) {
870 final int appWidgetId = (data != null) ? data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID🔵
871 if (resultCode == RESULT_CANCELED) {
872 completeTwoStageWidgetDrop(RESULT_CANCELED, appWidgetId);
873 mWorkspace.removeExtraEmptyScreenDelayed(true, exitSpringLoaded, ON_ACTIVITY_RESULT_ANIMA🔵
874 } else if (resultCode == RESULT_OK) {
875 addAppWidgetImpl(appWidgetId, mPendingAddInfo, null, mPendingAddWidgetInfo, ON_ACTIVITY_R🔵
876 }
877 return;
878 } else if (requestCode == REQUEST_PICK_WALLPAPER) {
879 if ((resultCode == RESULT_OK) && mWorkspace.isInOverviewMode()) {
880 showWorkspace(false);
881 }
882 return;
883 }
884 boolean isWidgetDrop = (requestCode == REQUEST_PICK_APPWIDGET) || (requestCode == REQUEST_CREATE_🔵
885 final boolean workspaceLocked = isWorkspaceLocked();
886 // We have special handling for widgets
887 if (isWidgetDrop) {
888 final int appWidgetId;
889 int widgetId = (data != null) ? data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1) : -🔵
890 if (widgetId < 0) {
891 appWidgetId = pendingAddWidgetId;
892 } else {
893 appWidgetId = widgetId;
894 }
895 final int result;
896 if ((appWidgetId < 0) || (resultCode == RESULT_CANCELED)) {
897 Log.e(TAG, "Error: appWidgetId (EXTRA_APPWIDGET_ID) was not " + "returned from the widget🔵
898 result = RESULT_CANCELED;
899 completeTwoStageWidgetDrop(result, appWidgetId);
900 final Runnable onComplete = new Runnable() {
901 @Override
902 public void run() {
903 exitSpringLoadedDragModeDelayed(false, 0, null);
904 }
905 };
906 if (workspaceLocked) {
907 // No need to remove the empty screen if we're mid-binding, as the
908 // the bind will not add the empty screen.
909 mWorkspace.postDelayed(onComplete, ON_ACTIVITY_RESULT_ANIMATION_DELAY);
910 } else {
911 mWorkspace.removeExtraEmptyScreenDelayed(true, onComplete, ON_ACTIVITY_RESULT_ANIMATI🔵
912 }
913 } else if (!workspaceLocked) {
914 if (mPendingAddInfo.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
915 // When the screen id represents an actual screen (as opposed to a rank)
916 // we make sure that the drop page actually exists.
917 mPendingAddInfo.screenId = ensurePendingDropLayoutExists(mPendingAddInfo.screenId);
918 }
919 final CellLayout dropLayout = mWorkspace.getScreenWithId(mPendingAddInfo.screenId);
920 dropLayout.setDropPending(true);
921 final Runnable onComplete = new Runnable() {
922 @Override
923 public void run() {
924 completeTwoStageWidgetDrop(resultCode, appWidgetId);
925 dropLayout.setDropPending(false);
926 }
927 };
928 mWorkspace.removeExtraEmptyScreenDelayed(true, onComplete, ON_ACTIVITY_RESULT_ANIMATION_D🔵
929 } else {
930 PendingAddArguments args = preparePendingAddArgs(requestCode, data, appWidgetId, mPending🔵
931 sPendingAddItem = args;
932 }
933 return;
934 }
935 if (requestCode == REQUEST_RECONFIGURE_APPWIDGET) {
936 if (resultCode == RESULT_OK) {
937 // Update the widget view.
938 PendingAddArguments args = preparePendingAddArgs(requestCode, data, pendingAddWidgetId, m🔵
939 if (workspaceLocked) {
940 sPendingAddItem = args;
941 } else {
942 completeAdd(args);
943 }
944 }
945 // Leave the widget in the pending state if the user canceled the configure.
946 return;
947 }
948 // The pattern used here is that a user PICKs a specific application,
949 // which, depending on the target, might need to CREATE the actual target.
950 // For example, the user would PICK_SHORTCUT for "Music playlist", and we
951 // launch over to the Music app to actually CREATE_SHORTCUT.
952 if ((resultCode == RESULT_OK) && (mPendingAddInfo.container != ItemInfo.NO_ID)) {
953 final PendingAddArguments args = preparePendingAddArgs(requestCode, data, -1, mPendingAddInfo🔵
954 if (isWorkspaceLocked()) {
955 sPendingAddItem = args;
956 } else {
957 completeAdd(args);
958 mWorkspace.removeExtraEmptyScreenDelayed(true, exitSpringLoaded, ON_ACTIVITY_RESULT_ANIMA🔵
959 }
960 } else if (resultCode == RESULT_CANCELED) {
961 mWorkspace.removeExtraEmptyScreenDelayed(true, exitSpringLoaded, ON_ACTIVITY_RESULT_ANIMATION🔵
962 }
963 mDragLayer.clearAnimatedView();
964 }
965
966 @Override
967 protected void onActivityResult(
968 final int requestCode, final int resultCode, final Intent data) {
969 handleActivityResult(requestCode, resultCode, data);
970 if (mLauncherCallbacks != null) {
971 mLauncherCallbacks.onActivityResult(requestCode, resultCode, data);
972 }
973 }
974
975 private PendingAddArguments preparePendingAddArgs(int requestCode, Intent data, int
976 appWidgetId, ItemInfo info) {
977 PendingAddArguments args = new PendingAddArguments();
978 args.requestCode = requestCode;
979 args.intent = data;
980 args.container = info.container;
981 args.screenId = info.screenId;
982 args.cellX = info.cellX;
983 args.cellY = info.cellY;
984 args.appWidgetId = appWidgetId;
985 return args;
986 }
987
988 /**
989 * Check to see if a given screen id exists. If not, create it at the end, return the new id.
990 *
991 * @param screenId the screen id to check
992 * @return the new screen, or screenId if it exists
993 */
994 private long ensurePendingDropLayoutExists(long screenId) {
995 CellLayout dropLayout =
996 (CellLayout) mWorkspace.getScreenWithId(screenId);
997 if (dropLayout == null) {
998 // it's possible that the add screen was removed because it was
999 // empty and a re-bind occurred
1000 mWorkspace.addExtraEmptyScreen();
1001 return mWorkspace.commitExtraEmptyScreen();
1002 } else {
1003 return screenId;
1004 }
1005 }
1006
1007 @Thunk
1008 void completeTwoStageWidgetDrop(final int resultCode, final int appWidgetId) {
1009 CellLayout cellLayout = ((CellLayout) (mWorkspace.getScreenWithId(mPendingAddInfo.screenId)));
1010 Runnable onCompleteRunnable = null;
1011 int animationType = 0;
1012 AppWidgetHostView boundWidget = null;
1013 if (resultCode == RESULT_OK) {
1014 animationType = Workspace.COMPLETE_TWO_STAGE_WIDGET_DROP_ANIMATION;
1015 final AppWidgetHostView layout = mAppWidgetHost.createView(this, appWidgetId, mPendingAddWidg🔵
1016 boundWidget = layout;
1017 onCompleteRunnable = new Runnable() {
1018 @Override
1019 public void run() {
1020 completeAddAppWidget(appWidgetId, mPendingAddInfo.container, mPendingAddInfo.screenId🔵
1021 exitSpringLoadedDragModeDelayed(resultCode != RESULT_CANCELED, EXIT_SPRINGLOADED_MODE🔵
1022 }
1023 };
1024 } else if (resultCode == RESULT_CANCELED) {
1025 mAppWidgetHost.deleteAppWidgetId(appWidgetId);
1026 animationType = Workspace.CANCEL_TWO_STAGE_WIDGET_DROP_ANIMATION;
1027 }
1028 if (mDragLayer.getAnimatedView() != null) {
1029 mWorkspace.animateWidgetDrop(mPendingAddInfo, cellLayout, ((DragView) (mDragLayer.getAnimated🔵
1030 } else if (onCompleteRunnable != null) {
1031 // The animated view may be null in the case of a rotation during widget configuration
1032 onCompleteRunnable.run();
1033 }
1034 }
1035
1036 @Override
1037 protected void onStop() {
1038 super.onStop();
1039 FirstFrameAnimatorHelper.setIsVisible(false);
1040
1041 if (mLauncherCallbacks != null) {
1042 mLauncherCallbacks.onStop();
1043 }
1044 }
1045
1046 @Override
1047 protected void onStart() {
1048 super.onStart();
1049 FirstFrameAnimatorHelper.setIsVisible(true);
1050
1051 if (mLauncherCallbacks != null) {
1052 mLauncherCallbacks.onStart();
1053 }
1054 }
1055
1056 @Override
1057 protected void onResume() {
1058 long startTime = 0;
1059 if (DEBUG_RESUME_TIME) {
1060 startTime = System.currentTimeMillis();
1061 Log.v(TAG, "Launcher.onResume()");
1062 }
1063 if (mLauncherCallbacks != null) {
1064 mLauncherCallbacks.preOnResume();
1065 }
1066 super.onResume();
1067 // Restore the previous launcher state
1068 if (mOnResumeState == State.WORKSPACE) {
1069 showWorkspace(false);
1070 } else if (mOnResumeState == State.APPS) {
1071 boolean launchedFromApp = mWaitingForResume != null;
1072 // Don't update the predicted apps if the user is returning to launcher in the apps
1073 // view after launching an app, as they may be depending on the UI to be static to
1074 // switch to another app, otherwise, if it was
1075 /* animated */
1076 /* resetListToTop */
1077 /* updatePredictedApps */
1078 showAppsView(false, false, !launchedFromApp);
1079 } else if (mOnResumeState == State.WIDGETS) {
1080 showWidgetsView(false, false);
1081 }
1082 mOnResumeState = State.NONE;
1083 // Restore the apps state if we are in all apps
1084 if (!Launcher.DISABLE_ALL_APPS_SEARCH_INTEGRATION) {
1085 // Otherwise, notify the callbacks if we are in all apps mode
1086 if (mState == State.APPS) {
1087 if (mLauncherCallbacks != null) {
1088 mLauncherCallbacks.onAllAppsShown();
1089 }
1090 }
1091 }
1092 // Background was set to gradient in onPause(), restore to transparent if in all apps.
1093 setWorkspaceBackground(mState == State.WORKSPACE ? WORKSPACE_BACKGROUND_TRANSPARENT : WORKSPACE_B🔵
1094 mPaused = false;
1095 if (mRestoring || mOnResumeNeedsLoad) {
1096 setWorkspaceLoading(true);
1097 mModel.startLoader(PagedView.INVALID_RESTORE_PAGE);
1098 mRestoring = false;
1099 mOnResumeNeedsLoad = false;
1100 }
1101 if (mBindOnResumeCallbacks.size() > 0) {
1102 // We might have postponed some bind calls until onResume (see waitUntilResume) --
1103 // execute them here
1104 long startTimeCallbacks = 0;
1105 if (DEBUG_RESUME_TIME) {
1106 startTimeCallbacks = System.currentTimeMillis();
1107 }
1108 for (int i = 0; i < mBindOnResumeCallbacks.size(); i++) {
1109 mBindOnResumeCallbacks.get(i).run();
1110 }
1111 mBindOnResumeCallbacks.clear();
1112 if (DEBUG_RESUME_TIME) {
1113 Log.d(TAG, "Time spent processing callbacks in onResume: " + (System.currentTimeMillis() 🔵
1114 }
1115 }
1116 if (mOnResumeCallbacks.size() > 0) {
1117 for (int i = 0; i < mOnResumeCallbacks.size(); i++) {
1118 mOnResumeCallbacks.get(i).run();
1119 }
1120 mOnResumeCallbacks.clear();
1121 }
1122 // Reset the pressed state of icons that were locked in the press state while activities
1123 // were launching
1124 if (mWaitingForResume != null) {
1125 // Resets the previous workspace icon press state
1126 mWaitingForResume.setStayPressed(false);
1127 }
1128 // It is possible that widgets can receive updates while launcher is not in the foreground.
1129 // Consequently, the widgets will be inflated in the orientation of the foreground activity
1130 // (framework issue). On resuming, we ensure that any widgets are inflated for the current
1131 // orientation.
1132 getWorkspace().reinflateWidgetsIfNecessary();
1133 reinflateQSBIfNecessary();
1134 if (DEBUG_RESUME_TIME) {
1135 Log.d(TAG, "Time spent in onResume: " + (System.currentTimeMillis() - startTime));
1136 }
1137 if (mWorkspace.getCustomContentCallbacks() != null) {
1138 // If we are resuming and the custom content is the current page, we call onShow().
1139 // It is also poassible that onShow will instead be called slightly after first layout
1140 // if PagedView#setRestorePage was set to the custom content page in onCreate().
1141 if (mWorkspace.isOnOrMovingToCustomContent()) {
1142 mWorkspace.getCustomContentCallbacks().onShow(true);
1143 }
1144 }
1145 updateInteraction(Workspace.State.NORMAL, mWorkspace.getState());
1146 mWorkspace.onResume();
1147 if (!isWorkspaceLoading()) {
1148 // Process any items that were added while Launcher was away.
1149 InstallShortcutReceiver.disableAndFlushInstallQueue(this);
1150 }
1151 if (mLauncherCallbacks != null) {
1152 mLauncherCallbacks.onResume();
1153 }
1154 }
1155
1156 @Override
1157 protected void onPause() {
1158 // Ensure that items added to Launcher are queued until Launcher returns
1159 InstallShortcutReceiver.enableInstallQueue();
1160 super.onPause();
1161 mPaused = true;
1162 mDragController.cancelDrag();
1163 mDragController.resetLastGestureUpTime();
1164 // We call onHide() aggressively. The custom content callbacks should be able to
1165 // debounce excess onHide calls.
1166 if (mWorkspace.getCustomContentCallbacks() != null) {
1167 mWorkspace.getCustomContentCallbacks().onHide();
1168 }
1169 if (mLauncherCallbacks != null) {
1170 mLauncherCallbacks.onPause();
1171 }
1172 }
1173
1174 public interface CustomContentCallbacks {
1175 // Custom content is completely shown. {@code fromResume} indicates whether this was caused
1176 // by a onResume or by scrolling otherwise.
1177 public abstract void onShow(boolean fromResume);
1178
1179 // Custom content is completely hidden
1180 public abstract void onHide();
1181
1182 // Custom content scroll progress changed. From 0 (not showing) to 1 (fully showing).
1183 public abstract void onScrollProgressChanged(float progress);
1184
1185 // Indicates whether the user is allowed to scroll away from the custom content.
1186 public abstract boolean isScrollingAllowed();
1187 }
1188
1189 public interface LauncherOverlay {
1190 /**
1191 * Touch interaction leading to overscroll has begun
1192 */
1193 public abstract void onScrollInteractionBegin();
1194
1195 /**
1196 * Touch interaction related to overscroll has ended
1197 */
1198 public abstract void onScrollInteractionEnd();
1199
1200 /**
1201 * Scroll progress, between 0 and 100, when the user scrolls beyond the leftmost
1202 * screen (or in the case of RTL, the rightmost screen).
1203 */
1204 public abstract void onScrollChange(int progress, boolean rtl);
1205
1206 /**
1207 * Screen has stopped scrolling
1208 */
1209 public abstract void onScrollSettled();
1210
1211 /**
1212 * This method can be called by the Launcher in order to force the LauncherOverlay
1213 * to exit fully immersive mode.
1214 */
1215 public abstract void forceExitFullImmersion();
1216 }
1217
1218 public interface LauncherAppsCallbacks {
1219 /**
1220 * Updates launcher to the available space that AllApps can take so as not to overlap with
1221 * any other views.
1222 */
1223 public abstract void onAllAppsBoundsChanged(Rect bounds);
1224
1225 /**
1226 * Called to dismiss all apps if it is showing.
1227 */
1228 public abstract void dismissAllApps();
1229 }
1230
1231 public interface LauncherOverlayCallbacks {
1232 /**
1233 * This method indicates whether a call to {@link #enterFullImmersion()} will succeed,
1234 * however it doesn't modify any state within the launcher.
1235 */
1236 public abstract boolean canEnterFullImmersion();
1237
1238 /**
1239 * Should be called to tell Launcher that the LauncherOverlay will take over interaction,
1240 * eg. by occupying the full screen and handling all touch events.
1241 *
1242 * @return true if Launcher allows the LauncherOverlay to become fully immersive. In this
1243 case, Launcher will modify any necessary state and assumes the overlay is
1244 handling all interaction. If false, the LauncherOverlay should cancel any
1245 */
1246 public abstract boolean enterFullImmersion();
1247
1248 /**
1249 * Must be called when exiting fully immersive mode. Indicates to Launcher that it has
1250 * full control over UI and state.
1251 */
1252 public abstract void exitFullImmersion();
1253 }
1254
1255 class LauncherOverlayCallbacksImpl implements LauncherOverlayCallbacks {
1256 @Override
1257 public boolean canEnterFullImmersion() {
1258 return mState == State.WORKSPACE;
1259 }
1260
1261 @Override
1262 public boolean enterFullImmersion() {
1263 if (mState == State.WORKSPACE) {
1264 // When fully immersed, disregard any touches which fall through.
1265 mDragLayer.setBlockTouch(true);
1266 return true;
1267 }
1268 return false;
1269 }
1270
1271 @Override
1272 public void exitFullImmersion() {
1273 mDragLayer.setBlockTouch(false);
1274 }
1275 }
1276
1277 protected boolean hasSettings() {
1278 if (mLauncherCallbacks != null) {
1279 return mLauncherCallbacks.hasSettings();
1280 }
1281 return false;
1282 }
1283
1284 public void addToCustomContentPage(View customContent,
1285 CustomContentCallbacks callbacks, String description) {
1286 mWorkspace.addToCustomContentPage(customContent, callbacks, description);
1287 }
1288
1289 // The custom content needs to offset its content to account for the QSB
1290 public int getTopOffsetForCustomContent() {
1291 return mWorkspace.getPaddingTop();
1292 }
1293
1294 @Override
1295 public Object onRetainNonConfigurationInstance() {
1296 // Flag the loader to stop early before switching
1297 if (mModel.isCurrentCallbacks(this)) {
1298 mModel.stopLoader();
1299 }
1300 //TODO(hyunyoungs): stop the widgets loader when there is a rotation.
1301 return Boolean.TRUE;
1302 }
1303
1304 // We can't hide the IME if it was forced open. So don't bother
1305 @Override
1306 public void onWindowFocusChanged(boolean hasFocus) {
1307 super.onWindowFocusChanged(hasFocus);
1308 mHasFocus = hasFocus;
1309
1310 if (mLauncherCallbacks != null) {
1311 mLauncherCallbacks.onWindowFocusChanged(hasFocus);
1312 }
1313 }
1314
1315 private boolean acceptFilter() {
1316 final InputMethodManager inputManager = (InputMethodManager)
1317 getSystemService(Context.INPUT_METHOD_SERVICE);
1318 return !inputManager.isFullscreenMode();
1319 }
1320
1321 @Override
1322 public boolean onKeyDown(int keyCode, KeyEvent event) {
1323 final int uniChar = event.getUnicodeChar();
1324 final boolean handled = super.onKeyDown(keyCode, event);
1325 final boolean isKeyNotWhitespace = uniChar > 0 && !Character.isWhitespace(uniChar);
1326 if (!handled && acceptFilter() && isKeyNotWhitespace) {
1327 boolean gotKey = TextKeyListener.getInstance().onKeyDown(mWorkspace, mDefaultKeySsb,
1328 keyCode, event);
1329 if (gotKey && mDefaultKeySsb != null && mDefaultKeySsb.length() > 0) {
1330 // something usable has been typed - start a search
1331 // the typed text will be retrieved and cleared by
1332 // showSearchDialog()
1333 // If there are multiple keystrokes before the search dialog takes focus,
1334 // onSearchRequested() will be called for every keystroke,
1335 // but it is idempotent, so it's fine.
1336 return onSearchRequested();
1337 }
1338 }
1339
1340 // Eat the long press event so the keyboard doesn't come up.
1341 if (keyCode == KeyEvent.KEYCODE_MENU && event.isLongPress()) {
1342 return true;
1343 }
1344
1345 return handled;
1346 }
1347
1348 private String getTypedText() {
1349 return mDefaultKeySsb.toString();
1350 }
1351
1352 private void clearTypedText() {
1353 mDefaultKeySsb.clear();
1354 mDefaultKeySsb.clearSpans();
1355 Selection.setSelection(mDefaultKeySsb, 0);
1356 }
1357
1358 /**
1359 * Given the integer (ordinal) value of a State enum instance, convert it to a variable of type
1360 * State
1361 */
1362 private static State intToState(int stateOrdinal) {
1363 State state = State.WORKSPACE;
1364 final State[] stateValues = State.values();
1365 for (int i = 0; i < stateValues.length; i++) {
1366 if (stateValues[i].ordinal() == stateOrdinal) {
1367 state = stateValues[i];
1368 break;
1369 }
1370 }
1371 return state;
1372 }
1373
1374 /**
1375 * Restores the previous state, if it exists.
1376 *
1377 * @param savedState The previous state.
1378 */
1379 @SuppressWarnings("unchecked")
1380 private void restoreState(Bundle savedState) {
1381 if (savedState == null) {
1382 return;
1383 }
1384 State state = intToState(savedState.getInt(RUNTIME_STATE, State.WORKSPACE.ordinal()));
1385 if ((state == State.APPS) || (state == State.WIDGETS)) {
1386 mOnResumeState = state;
1387 }
1388 int currentScreen = savedState.getInt(RUNTIME_STATE_CURRENT_SCREEN, PagedView.INVALID_RESTORE_PAG🔵
1389 if (currentScreen != PagedView.INVALID_RESTORE_PAGE) {
1390 mWorkspace.setRestorePage(currentScreen);
1391 }
1392 final long pendingAddContainer = savedState.getLong(RUNTIME_STATE_PENDING_ADD_CONTAINER, -1);
1393 final long pendingAddScreen = savedState.getLong(RUNTIME_STATE_PENDING_ADD_SCREEN, -1);
1394 if ((pendingAddContainer != ItemInfo.NO_ID) && (pendingAddScreen > (-1))) {
1395 mPendingAddInfo.container = pendingAddContainer;
1396 mPendingAddInfo.screenId = pendingAddScreen;
1397 mPendingAddInfo.cellX = savedState.getInt(RUNTIME_STATE_PENDING_ADD_CELL_X);
1398 mPendingAddInfo.cellY = savedState.getInt(RUNTIME_STATE_PENDING_ADD_CELL_Y);
1399 mPendingAddInfo.spanX = savedState.getInt(RUNTIME_STATE_PENDING_ADD_SPAN_X);
1400 mPendingAddInfo.spanY = savedState.getInt(RUNTIME_STATE_PENDING_ADD_SPAN_Y);
1401 AppWidgetProviderInfo info = savedState.getParcelable(RUNTIME_STATE_PENDING_ADD_WIDGET_INFO);
1402 mPendingAddWidgetInfo = (info == null) ? null : LauncherAppWidgetProviderInfo.fromProviderInf🔵
1403 mPendingAddWidgetId = savedState.getInt(RUNTIME_STATE_PENDING_ADD_WIDGET_ID);
1404 setWaitingForResult(true);
1405 mRestoring = true;
1406 }
1407 mItemIdToViewId = ((HashMap<Integer, Integer>) (savedState.getSerializable(RUNTIME_STATE_VIEW_IDS🔵
1408 }
1409
1410 /**
1411 * Finds all the views we need and configure them properly.
1412 */
1413 private void setupViews() {
1414 final DragController dragController = mDragController;
1415 mLauncherView = findViewById(R.id.launcher);
1416 mFocusHandler = ((FocusIndicatorView) (findViewById(R.id.focus_indicator)));
1417 mDragLayer = ((DragLayer) (findViewById(R.id.drag_layer)));
1418 mWorkspace = ((Workspace) (mDragLayer.findViewById(R.id.workspace)));
1419 mWorkspace.setPageSwitchListener(this);
1420 mPageIndicators = mDragLayer.findViewById(R.id.page_indicator);
1421 mLauncherView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_L🔵
1422 mWorkspaceBackgroundDrawable = getResources().getDrawable(R.drawable.workspace_bg);
1423 // Setup the drag layer
1424 mDragLayer.setup(this, dragController);
1425 // Setup the hotseat
1426 mHotseat = ((Hotseat) (findViewById(R.id.hotseat)));
1427 if (mHotseat != null) {
1428 mHotseat.setOnLongClickListener(this);
1429 }
1430 mOverviewPanel = ((ViewGroup) (findViewById(R.id.overview_panel)));
1431 View widgetButton = findViewById(R.id.widget_button);
1432 widgetButton.setOnClickListener(new OnClickListener() {
1433 @Override
1434 public void onClick(View arg0) {
1435 if (!mWorkspace.isSwitchingState()) {
1436 onClickAddWidgetButton(arg0);
1437 }
1438 }
1439 });
1440 widgetButton.setOnTouchListener(getHapticFeedbackTouchListener());
1441 View wallpaperButton = findViewById(R.id.wallpaper_button);
1442 wallpaperButton.setOnClickListener(new OnClickListener() {
1443 @Override
1444 public void onClick(View arg0) {
1445 if (!mWorkspace.isSwitchingState()) {
1446 onClickWallpaperPicker(arg0);
1447 }
1448 }
1449 });
1450 wallpaperButton.setOnTouchListener(getHapticFeedbackTouchListener());
1451 View settingsButton = findViewById(R.id.settings_button);
1452 if (hasSettings()) {
1453 settingsButton.setOnClickListener(new OnClickListener() {
1454 @Override
1455 public void onClick(View arg0) {
1456 if (!mWorkspace.isSwitchingState()) {
1457 onClickSettingsButton(arg0);
1458 }
1459 }
1460 });
1461 settingsButton.setOnTouchListener(getHapticFeedbackTouchListener());
1462 } else {
1463 settingsButton.setVisibility(View.GONE);
1464 }
1465 mOverviewPanel.setAlpha(0.0F);
1466 // Setup the workspace
1467 mWorkspace.setHapticFeedbackEnabled(false);
1468 mWorkspace.setOnLongClickListener(this);
1469 mWorkspace.setup(dragController);
1470 dragController.addDragListener(mWorkspace);
1471 // Get the search/delete bar
1472 mSearchDropTargetBar = ((SearchDropTargetBar) (mDragLayer.findViewById(R.id.search_drop_target_ba🔵
1473 // Setup Apps
1474 mAppsView = ((AllAppsContainerView) (findViewById(R.id.apps_view)));
1475 if (isAllAppsSearchOverridden()) {
1476 mAppsView.hideHeaderBar();
1477 }
1478 // Setup AppsCustomize
1479 mWidgetsView = ((WidgetsContainerView) (findViewById(R.id.widgets_view)));
1480 // Setup the drag controller (drop targets have to be added in reverse order in priority)
1481 dragController.setDragScoller(mWorkspace);
1482 dragController.setScrollView(mDragLayer);
1483 dragController.setMoveTarget(mWorkspace);
1484 dragController.addDropTarget(mWorkspace);
1485 if (mSearchDropTargetBar != null) {
1486 mSearchDropTargetBar.setup(this, dragController);
1487 mSearchDropTargetBar.setQsbSearchBar(getOrCreateQsbBar());
1488 }
1489 if (getResources().getBoolean(R.bool.debug_memory_enabled)) {
1490 Log.v(TAG, "adding WeightWatcher");
1491 mWeightWatcher = new WeightWatcher(this);
1492 mWeightWatcher.setAlpha(0.5F);
1493 ((FrameLayout) (mLauncherView)).addView(mWeightWatcher, new FrameLayout.LayoutParams(FrameLay🔵
1494 boolean show = shouldShowWeightWatcher();
1495 mWeightWatcher.setVisibility(show ? View.VISIBLE : View.GONE);
1496 }
1497 }
1498
1499 /**
1500 * Sets the all apps button. This method is called from {@link Hotseat}.
1501 */
1502 public void setAllAppsButton(View allAppsButton) {
1503 mAllAppsButton = allAppsButton;
1504 }
1505
1506 public View getAllAppsButton() {
1507 return mAllAppsButton;
1508 }
1509
1510 /**
1511 * Creates a view representing a shortcut.
1512 *
1513 * @param info
1514 * The data structure describing the shortcut.
1515 */
1516 View createShortcut(ShortcutInfo info) {
1517 return createShortcut(((ViewGroup) (mWorkspace.getChildAt(mWorkspace.getCurrentPage()))), info);
1518 }
1519
1520 /**
1521 * Creates a view representing a shortcut inflated from the specified resource.
1522 *
1523 * @param parent The group the shortcut belongs to.
1524 * @param info The data structure describing the shortcut.
1525 *
1526 * @return A View inflated from layoutResId.
1527 */
1528 public View createShortcut(ViewGroup parent, ShortcutInfo info) {
1529 BubbleTextView favorite = ((BubbleTextView) (mInflater.inflate(R.layout.app_icon, parent, false))🔵
1530 favorite.applyFromShortcutInfo(info, mIconCache);
1531 favorite.setCompoundDrawablePadding(mDeviceProfile.iconDrawablePaddingPx);
1532 favorite.setOnClickListener(this);
1533 favorite.setOnFocusChangeListener(mFocusHandler);
1534 return favorite;
1535 }
1536
1537 /**
1538 * Add a shortcut to the workspace.
1539 *
1540 * @param data The intent describing the shortcut.
1541 * @param cellInfo The position on screen where to create the shortcut.
1542 */
1543 private void completeAddShortcut(Intent data, long container, long screenId, int cellX, int cellY) {
1544 int[] cellXY = mTmpAddItemCellCoordinates;
1545 int[] touchXY = mPendingAddInfo.dropPos;
1546 CellLayout layout = getCellLayout(container, screenId);
1547 ShortcutInfo info = InstallShortcutReceiver.fromShortcutIntent(this, data);
1548 if (info == null) {
1549 return;
1550 }
1551 final View view = createShortcut(info);
1552 boolean foundCellSpan = false;
1553 // First we check if we already know the exact location where we want to add this item.
1554 if ((cellX >= 0) && (cellY >= 0)) {
1555 cellXY[0] = cellX;
1556 cellXY[1] = cellY;
1557 foundCellSpan = true;
1558 // If appropriate, either create a folder or add to an existing folder
1559 if (mWorkspace.createUserFolderIfNecessary(view, container, layout, cellXY, 0, true, null, nu🔵
1560 return;
1561 }
1562 DragObject dragObject = new DragObject();
1563 dragObject.dragInfo = info;
1564 if (mWorkspace.addToExistingFolderIfNecessary(view, layout, cellXY, 0, dragObject, true)) {
1565 return;
1566 }
1567 } else if (touchXY != null) {
1568 // when dragging and dropping, just find the closest free spot
1569 int[] result = layout.findNearestVacantArea(touchXY[0], touchXY[1], 1, 1, cellXY);
1570 foundCellSpan = result != null;
1571 } else {
1572 foundCellSpan = layout.findCellForSpan(cellXY, 1, 1);
1573 }
1574 if (!foundCellSpan) {
1575 showOutOfSpaceMessage(isHotseatLayout(layout));
1576 return;
1577 }
1578 LauncherModel.addItemToDatabase(this, info, container, screenId, cellXY[0], cellXY[1]);
1579 if (!mRestoring) {
1580 mWorkspace.addInScreen(view, container, screenId, cellXY[0], cellXY[1], 1, 1, isWorkspaceLock🔵
1581 }
1582 }
1583
1584 private int[] getSpanForWidget(ComponentName component, int minWidth, int minHeight) {
1585 Rect padding = AppWidgetHostView.getDefaultPaddingForWidget(this, component, null);
1586 // We want to account for the extra amount of padding that we are adding to the widget
1587 // to ensure that it gets the full amount of space that it has requested
1588 int requiredWidth = (minWidth + padding.left) + padding.right;
1589 int requiredHeight = (minHeight + padding.top) + padding.bottom;
1590 return CellLayout.rectToCell(this, requiredWidth, requiredHeight, null);
1591 }
1592
1593 public int[] getSpanForWidget(AppWidgetProviderInfo info) {
1594 return getSpanForWidget(info.provider, info.minWidth, info.minHeight);
1595 }
1596
1597 public int[] getMinSpanForWidget(AppWidgetProviderInfo info) {
1598 return getSpanForWidget(info.provider, info.minResizeWidth, info.minResizeHeight);
1599 }
1600
1601 /**
1602 * Add a widget to the workspace.
1603 *
1604 * @param appWidgetId The app widget id
1605 */
1606 @Thunk
1607 void completeAddAppWidget(int appWidgetId, long container, long screenId, AppWidgetHostView hostView,🔵
1608 ItemInfo info = mPendingAddInfo;
1609 if (appWidgetInfo == null) {
1610 appWidgetInfo = LauncherAppWidgetProviderInfo.fromProviderInfo(this, mAppWidgetManager.getApp🔵
1611 }
1612 if (appWidgetInfo.isCustomWidget) {
1613 appWidgetId = LauncherAppWidgetInfo.CUSTOM_WIDGET_ID;
1614 }
1615 LauncherAppWidgetInfo launcherInfo;
1616 launcherInfo = new LauncherAppWidgetInfo(appWidgetId, appWidgetInfo.provider);
1617 launcherInfo.spanX = info.spanX;
1618 launcherInfo.spanY = info.spanY;
1619 launcherInfo.minSpanX = info.minSpanX;
1620 launcherInfo.minSpanY = info.minSpanY;
1621 launcherInfo.user = mAppWidgetManager.getUser(appWidgetInfo);
1622 LauncherModel.addItemToDatabase(this, launcherInfo, container, screenId, info.cellX, info.cellY);
1623 if (!mRestoring) {
1624 if (hostView == null) {
1625 // Perform actual inflation because we're live
1626 launcherInfo.hostView = mAppWidgetHost.createView(this, appWidgetId, appWidgetInfo);
1627 } else {
1628 // The AppWidgetHostView has already been inflated and instantiated
1629 launcherInfo.hostView = hostView;
1630 }
1631 launcherInfo.hostView.setTag(launcherInfo);
1632 launcherInfo.hostView.setVisibility(View.VISIBLE);
1633 launcherInfo.notifyWidgetSizeChanged(this);
1634 mWorkspace.addInScreen(launcherInfo.hostView, container, screenId, info.cellX, info.cellY, la🔵
1635 addWidgetToAutoAdvanceIfNeeded(launcherInfo.hostView, appWidgetInfo);
1636 }
1637 resetAddInfo();
1638 }
1639
1640 private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
1641 @Override
1642 public void onReceive(Context context, Intent intent) {
1643 final String action = intent.getAction();
1644 if (Intent.ACTION_SCREEN_OFF.equals(action)) {
1645 mUserPresent = false;
1646 mDragLayer.clearAllResizeFrames();
1647 updateAutoAdvanceState();
1648 // Reset AllApps to its initial state only if we are not in the middle of
1649 // processing a multi-step drop
1650 if (((mAppsView != null) && (mWidgetsView != null)) && (mPendingAddInfo.container == Item🔵
1651 showWorkspace(false);
1652 }
1653 } else if (Intent.ACTION_USER_PRESENT.equals(action)) {
1654 mUserPresent = true;
1655 updateAutoAdvanceState();
1656 } else if (ENABLE_DEBUG_INTENTS && DebugIntents.DELETE_DATABASE.equals(action)) {
1657 mModel.resetLoadedState(false, true);
1658 mModel.startLoader(PagedView.INVALID_RESTORE_PAGE, LauncherModel.LOADER_FLAG_CLEAR_WORKSP🔵
1659 } else if (ENABLE_DEBUG_INTENTS && DebugIntents.MIGRATE_DATABASE.equals(action)) {
1660 mModel.resetLoadedState(false, true);
1661 mModel.startLoader(PagedView.INVALID_RESTORE_PAGE, LauncherModel.LOADER_FLAG_CLEAR_WORKSP🔵
1662 }
1663 }
1664 };
1665
1666 @Override
1667 public void onAttachedToWindow() {
1668 super.onAttachedToWindow();
1669 // Listen for broadcasts related to user-presence
1670 final IntentFilter filter = new IntentFilter();
1671 filter.addAction(Intent.ACTION_SCREEN_OFF);
1672 filter.addAction(Intent.ACTION_USER_PRESENT);
1673 // For handling managed profiles
1674 if (ENABLE_DEBUG_INTENTS) {
1675 filter.addAction(DebugIntents.DELETE_DATABASE);
1676 filter.addAction(DebugIntents.MIGRATE_DATABASE);
1677 }
1678 registerReceiver(mReceiver, filter);
1679 FirstFrameAnimatorHelper.initializeDrawListener(getWindow().getDecorView());
1680 setupTransparentSystemBarsForLmp();
1681 mAttached = true;
1682 mVisible = true;
1683 }
1684
1685 /**
1686 * Sets up transparent navigation and status bars in LMP.
1687 * This method is a no-op for other platform versions.
1688 */
1689 @TargetApi(Build.VERSION_CODES.LOLLIPOP)
1690 private void setupTransparentSystemBarsForLmp() {
1691 if (Utilities.isLmpOrAbove()) {
1692 Window window = getWindow();
1693 window.getAttributes().systemUiVisibility |= (View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM🔵
1694 window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS | WindowManager.LayoutPa🔵
1695 window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
1696 window.setStatusBarColor(Color.TRANSPARENT);
1697 window.setNavigationBarColor(Color.TRANSPARENT);
1698 }
1699 }
1700
1701 @Override
1702 public void onDetachedFromWindow() {
1703 super.onDetachedFromWindow();
1704 mVisible = false;
1705 if (mAttached) {
1706 unregisterReceiver(mReceiver);
1707 mAttached = false;
1708 }
1709 updateAutoAdvanceState();
1710 }
1711
1712 public void onWindowVisibilityChanged(int visibility) {
1713 mVisible = visibility == View.VISIBLE;
1714 updateAutoAdvanceState();
1715 // The following code used to be in onResume, but it turns out onResume is called when
1716 // you're in All Apps and click home to go to the workspace. onWindowVisibilityChanged
1717 // is a more appropriate event to handle
1718 if (mVisible) {
1719 if (!mWorkspaceLoading) {
1720 final ViewTreeObserver observer = mWorkspace.getViewTreeObserver();
1721 // We want to let Launcher draw itself at least once before we force it to build
1722 // layers on all the workspace pages, so that transitioning to Launcher from other
1723 // apps is nice and speedy.
1724 observer.addOnDrawListener(new ViewTreeObserver.OnDrawListener() {
1725 private boolean mStarted = false;
1726
1727 public void onDraw() {
1728 if (mStarted) {
1729 return;
1730 }
1731 mStarted = true;
1732 // We delay the layer building a bit in order to give
1733 // other message processing a time to run. In particular
1734 // this avoids a delay in hiding the IME if it was
1735 // currently shown, because doing that may involve
1736 // some communication back with the app.
1737 mWorkspace.postDelayed(mBuildLayersRunnable, 500);
1738 final ViewTreeObserver.OnDrawListener listener = this;
1739 mWorkspace.post(new Runnable() {
1740 public void run() {
1741 if ((mWorkspace != null) && (mWorkspace.getViewTreeObserver() != null)) {
1742 mWorkspace.getViewTreeObserver().removeOnDrawListener(listener);
1743 }
1744 }
1745 });
1746 return;
1747 }
1748 });
1749 }
1750 clearTypedText();
1751 }
1752 }
1753
1754 @Thunk
1755 void sendAdvanceMessage(long delay) {
1756 mHandler.removeMessages(ADVANCE_MSG);
1757 Message msg = mHandler.obtainMessage(ADVANCE_MSG);
1758 mHandler.sendMessageDelayed(msg, delay);
1759 mAutoAdvanceSentTime = System.currentTimeMillis();
1760 }
1761
1762 @Thunk
1763 void updateAutoAdvanceState() {
1764 boolean autoAdvanceRunning = (mVisible && mUserPresent) && (!mWidgetsToAdvance.isEmpty());
1765 if (autoAdvanceRunning != mAutoAdvanceRunning) {
1766 mAutoAdvanceRunning = autoAdvanceRunning;
1767 if (autoAdvanceRunning) {
1768 long delay = (mAutoAdvanceTimeLeft == (-1)) ? mAdvanceInterval : mAutoAdvanceTimeLeft;
1769 sendAdvanceMessage(delay);
1770 } else {
1771 if (!mWidgetsToAdvance.isEmpty()) {
1772 mAutoAdvanceTimeLeft = Math.max(0, mAdvanceInterval - (System.currentTimeMillis() - m🔵
1773 }
1774 mHandler.removeMessages(ADVANCE_MSG);
1775 mHandler.removeMessages(0);// Remove messages sent using postDelayed()
1776
1777 }
1778 }
1779 }
1780
1781 private final Handler mHandler = new Handler() {
1782 @Override
1783 public void handleMessage(Message msg) {
1784 if (msg.what == ADVANCE_MSG) {
1785 int i = 0;
1786 for (View key: mWidgetsToAdvance.keySet()) {
1787 final View v = key.findViewById(mWidgetsToAdvance.get(key).autoAdvanceViewId);
1788 final int delay = mAdvanceStagger * i;
1789 if (v instanceof Advanceable) {
1790 postDelayed(new Runnable() {
1791 public void run() {
1792 ((Advanceable) v).advance();
1793 }
1794 }, delay);
1795 }
1796 i++;
1797 }
1798 sendAdvanceMessage(mAdvanceInterval);
1799 }
1800 }
1801 };
1802
1803 void addWidgetToAutoAdvanceIfNeeded(View hostView, AppWidgetProviderInfo appWidgetInfo) {
1804 if ((appWidgetInfo == null) || (appWidgetInfo.autoAdvanceViewId == (-1))) {
1805 return;
1806 }
1807 View v = hostView.findViewById(appWidgetInfo.autoAdvanceViewId);
1808 if (v instanceof Advanceable) {
1809 mWidgetsToAdvance.put(hostView, appWidgetInfo);
1810 ((Advanceable) (v)).fyiWillBeAdvancedByHostKThx();
1811 updateAutoAdvanceState();
1812 }
1813 }
1814
1815 void removeWidgetToAutoAdvance(View hostView) {
1816 if (mWidgetsToAdvance.containsKey(hostView)) {
1817 mWidgetsToAdvance.remove(hostView);
1818 updateAutoAdvanceState();
1819 }
1820 }
1821
1822 public void removeAppWidget(LauncherAppWidgetInfo launcherInfo) {
1823 removeWidgetToAutoAdvance(launcherInfo.hostView);
1824 launcherInfo.hostView = null;
1825 }
1826
1827 public void showOutOfSpaceMessage(boolean isHotseatLayout) {
1828 int strId = (isHotseatLayout) ? R.string.hotseat_out_of_space : R.string.out_of_space;
1829 Toast.makeText(this, getString(strId), Toast.LENGTH_SHORT).show();
1830 }
1831
1832 public DragLayer getDragLayer() {
1833 return mDragLayer;
1834 }
1835
1836 public AllAppsContainerView getAppsView() {
1837 return mAppsView;
1838 }
1839
1840 public WidgetsContainerView getWidgetsView() {
1841 return mWidgetsView;
1842 }
1843
1844 public Workspace getWorkspace() {
1845 return mWorkspace;
1846 }
1847
1848 public Hotseat getHotseat() {
1849 return mHotseat;
1850 }
1851
1852 public ViewGroup getOverviewPanel() {
1853 return mOverviewPanel;
1854 }
1855
1856 public SearchDropTargetBar getSearchBar() {
1857 return mSearchDropTargetBar;
1858 }
1859
1860 public LauncherAppWidgetHost getAppWidgetHost() {
1861 return mAppWidgetHost;
1862 }
1863
1864 public LauncherModel getModel() {
1865 return mModel;
1866 }
1867
1868 protected SharedPreferences getSharedPrefs() {
1869 return mSharedPrefs;
1870 }
1871
1872 public DeviceProfile getDeviceProfile() {
1873 return mDeviceProfile;
1874 }
1875
1876 public void closeSystemDialogs() {
1877 getWindow().closeAllPanels();
1878
1879 // Whatever we were doing is hereby canceled.
1880 setWaitingForResult(false);
1881 }
1882
1883 @Override
1884 protected void onNewIntent(Intent intent) {
1885 long startTime = 0;
1886 if (DEBUG_RESUME_TIME) {
1887 startTime = System.currentTimeMillis();
1888 }
1889 super.onNewIntent(intent);
1890 // Close the menu
1891 if (Intent.ACTION_MAIN.equals(intent.getAction())) {
1892 // also will cancel mWaitingForResult.
1893 closeSystemDialogs();
1894 final boolean alreadyOnHome = mHasFocus && ((intent.getFlags() & Intent.FLAG_ACTIVITY_BROUGHT🔵
1895 if (mWorkspace == null) {
1896 // Can be cases where mWorkspace is null, this prevents a NPE
1897 return;
1898 }
1899 Folder openFolder = mWorkspace.getOpenFolder();
1900 // In all these cases, only animate if we're already on home
1901 mWorkspace.exitWidgetResizeMode();
1902 boolean moveToDefaultScreen = (mLauncherCallbacks != null) ? mLauncherCallbacks.shouldMoveToD🔵
1903 if ((((alreadyOnHome && (mState == State.WORKSPACE)) && (!mWorkspace.isTouchActive())) && (op🔵
1904 mWorkspace.moveToDefaultScreen(true);
1905 }
1906 closeFolder();
1907 exitSpringLoadedDragMode();
1908 // If we are already on home, then just animate back to the workspace,
1909 // otherwise, just wait until onResume to set the state back to Workspace
1910 if (alreadyOnHome) {
1911 showWorkspace(true);
1912 } else {
1913 mOnResumeState = State.WORKSPACE;
1914 }
1915 final View v = getWindow().peekDecorView();
1916 if ((v != null) && (v.getWindowToken() != null)) {
1917 InputMethodManager imm = ((InputMethodManager) (getSystemService(INPUT_METHOD_SERVICE)));
1918 imm.hideSoftInputFromWindow(v.getWindowToken(), 0);
1919 }
1920 // Reset the apps view
1921 if ((!alreadyOnHome) && (mAppsView != null)) {
1922 mAppsView.scrollToTop();
1923 }
1924 // Reset the widgets view
1925 if ((!alreadyOnHome) && (mWidgetsView != null)) {
1926 mWidgetsView.scrollToTop();
1927 }
1928 if (mLauncherCallbacks != null) {
1929 mLauncherCallbacks.onHomeIntent();
1930 }
1931 }
1932 if (DEBUG_RESUME_TIME) {
1933 Log.d(TAG, "Time spent in onNewIntent: " + (System.currentTimeMillis() - startTime));
1934 }
1935 if (mLauncherCallbacks != null) {
1936 mLauncherCallbacks.onNewIntent(intent);
1937 }
1938 }
1939
1940 @Override
1941 public void onRestoreInstanceState(Bundle state) {
1942 super.onRestoreInstanceState(state);
1943 for (int page: mSynchronouslyBoundPages) {
1944 mWorkspace.restoreInstanceStateForChild(page);
1945 }
1946 }
1947
1948 @Override
1949 protected void onSaveInstanceState(Bundle outState) {
1950 if (mWorkspace.getChildCount() > 0) {
1951 outState.putInt(RUNTIME_STATE_CURRENT_SCREEN, mWorkspace.getCurrentPageOffsetFromCustomConten🔵
1952 }
1953 super.onSaveInstanceState(outState);
1954 outState.putInt(RUNTIME_STATE, mState.ordinal());
1955 // We close any open folder since it will not be re-opened, and we need to make sure
1956 // this state is reflected.
1957 closeFolder();
1958 if (((mPendingAddInfo.container != ItemInfo.NO_ID) && (mPendingAddInfo.screenId > (-1))) && mWait🔵
1959 outState.putLong(RUNTIME_STATE_PENDING_ADD_CONTAINER, mPendingAddInfo.container);
1960 outState.putLong(RUNTIME_STATE_PENDING_ADD_SCREEN, mPendingAddInfo.screenId);
1961 outState.putInt(RUNTIME_STATE_PENDING_ADD_CELL_X, mPendingAddInfo.cellX);
1962 outState.putInt(RUNTIME_STATE_PENDING_ADD_CELL_Y, mPendingAddInfo.cellY);
1963 outState.putInt(RUNTIME_STATE_PENDING_ADD_SPAN_X, mPendingAddInfo.spanX);
1964 outState.putInt(RUNTIME_STATE_PENDING_ADD_SPAN_Y, mPendingAddInfo.spanY);
1965 outState.putParcelable(RUNTIME_STATE_PENDING_ADD_WIDGET_INFO, mPendingAddWidgetInfo);
1966 outState.putInt(RUNTIME_STATE_PENDING_ADD_WIDGET_ID, mPendingAddWidgetId);
1967 }
1968 // Save the current widgets tray?
1969 // TODO(hyunyoungs)
1970 outState.putSerializable(RUNTIME_STATE_VIEW_IDS, mItemIdToViewId);
1971 if (mLauncherCallbacks != null) {
1972 mLauncherCallbacks.onSaveInstanceState(outState);
1973 }
1974 }
1975
1976 @Override
1977 public void onDestroy() {
1978 super.onDestroy();
1979 // Remove all pending runnables
1980 mHandler.removeMessages(ADVANCE_MSG);
1981 mHandler.removeMessages(0);
1982 mWorkspace.removeCallbacks(mBuildLayersRunnable);
1983 // Stop callbacks from LauncherModel
1984 LauncherAppState app = LauncherAppState.getInstance();
1985 // It's possible to receive onDestroy after a new Launcher activity has
1986 // been created. In this case, don't interfere with the new Launcher.
1987 if (mModel.isCurrentCallbacks(this)) {
1988 mModel.stopLoader();
1989 app.setLauncher(null);
1990 }
1991 try {
1992 mAppWidgetHost.stopListening();
1993 } catch (java.lang.NullPointerException ex) {
1994 Log.w(TAG, "problem while stopping AppWidgetHost during Launcher destruction", ex);
1995 }
1996 mAppWidgetHost = null;
1997 mWidgetsToAdvance.clear();
1998 TextKeyListener.getInstance().release();
1999 getContentResolver().unregisterContentObserver(mWidgetObserver);
2000 unregisterReceiver(mCloseSystemDialogsReceiver);
2001 mDragLayer.clearAllResizeFrames();
2002 ((ViewGroup) (mWorkspace.getParent())).removeAllViews();
2003 mWorkspace.removeAllWorkspaceScreens();
2004 mWorkspace = null;
2005 mDragController = null;
2006 LauncherAnimUtils.onDestroyActivity();
2007 if (mLauncherCallbacks != null) {
2008 mLauncherCallbacks.onDestroy();
2009 }
2010 }
2011
2012 public DragController getDragController() {
2013 return mDragController;
2014 }
2015
2016 @Override
2017 public void startActivityForResult(Intent intent, int requestCode) {
2018 onStartForResult(requestCode);
2019 super.startActivityForResult(intent, requestCode);
2020 }
2021
2022 @Override
2023 public void startIntentSenderForResult (IntentSender intent, int requestCode,
2024 Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags, Bundle options) {
2025 onStartForResult(requestCode);
2026 try {
2027 super.startIntentSenderForResult(intent, requestCode,
2028 fillInIntent, flagsMask, flagsValues, extraFlags, options);
2029 } catch (IntentSender.SendIntentException e) {
2030 throw new ActivityNotFoundException();
2031 }
2032 }
2033
2034 private void onStartForResult(int requestCode) {
2035 if (requestCode >= 0) {
2036 setWaitingForResult(true);
2037 }
2038 }
2039
2040 /**
2041 * Indicates that we want global search for this activity by setting the globalSearch
2042 * argument for {@link #startSearch} to true.
2043 */
2044 @Override
2045 public void startSearch(String initialQuery, boolean selectInitialQuery, Bundle appSearchData, boolea🔵
2046 if (initialQuery == null) {
2047 // Use any text typed in the launcher as the initial query
2048 initialQuery = getTypedText();
2049 }
2050 if (appSearchData == null) {
2051 appSearchData = new Bundle();
2052 appSearchData.putString("source", "launcher-search");
2053 }
2054 Rect sourceBounds = new Rect();
2055 if (mSearchDropTargetBar != null) {
2056 sourceBounds = mSearchDropTargetBar.getSearchBarBounds();
2057 }
2058 boolean clearTextImmediately = startSearch(initialQuery, selectInitialQuery, appSearchData, sourc🔵
2059 if (clearTextImmediately) {
2060 clearTypedText();
2061 }
2062 // We need to show the workspace after starting the search
2063 showWorkspace(true);
2064 }
2065
2066 /**
2067 * Start a text search.
2068 *
2069 * @return {@code true} if the search will start immediately, so any further keypresses
2070 * will be handled directly by the search UI. {@code false} if {@link Launcher} should continue
2071 * to buffer keypresses.
2072 */
2073 public boolean startSearch(String initialQuery,
2074 boolean selectInitialQuery, Bundle appSearchData, Rect sourceBounds) {
2075 if (mLauncherCallbacks != null && mLauncherCallbacks.providesSearch()) {
2076 return mLauncherCallbacks.startSearch(initialQuery, selectInitialQuery, appSearchData,
2077 sourceBounds);
2078 }
2079
2080 startGlobalSearch(initialQuery, selectInitialQuery,
2081 appSearchData, sourceBounds);
2082 return false;
2083 }
2084
2085 /**
2086 * Starts the global search activity. This code is a copied from SearchManager
2087 */
2088 private void startGlobalSearch(String initialQuery,
2089 boolean selectInitialQuery, Bundle appSearchData, Rect sourceBounds) {
2090 final SearchManager searchManager =
2091 (SearchManager) getSystemService(Context.SEARCH_SERVICE);
2092 ComponentName globalSearchActivity = searchManager.getGlobalSearchActivity();
2093 if (globalSearchActivity == null) {
2094 Log.w(TAG, "No global search activity found.");
2095 return;
2096 }
2097 Intent intent = new Intent(SearchManager.INTENT_ACTION_GLOBAL_SEARCH);
2098 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
2099 intent.setComponent(globalSearchActivity);
2100 // Make sure that we have a Bundle to put source in
2101 if (appSearchData == null) {
2102 appSearchData = new Bundle();
2103 } else {
2104 appSearchData = new Bundle(appSearchData);
2105 }
2106 // Set source to package name of app that starts global search if not set already.
2107 if (!appSearchData.containsKey("source")) {
2108 appSearchData.putString("source", getPackageName());
2109 }
2110 intent.putExtra(SearchManager.APP_DATA, appSearchData);
2111 if (!TextUtils.isEmpty(initialQuery)) {
2112 intent.putExtra(SearchManager.QUERY, initialQuery);
2113 }
2114 if (selectInitialQuery) {
2115 intent.putExtra(SearchManager.EXTRA_SELECT_QUERY, selectInitialQuery);
2116 }
2117 intent.setSourceBounds(sourceBounds);
2118 try {
2119 startActivity(intent);
2120 } catch (ActivityNotFoundException ex) {
2121 Log.e(TAG, "Global search activity not found: " + globalSearchActivity);
2122 }
2123 }
2124
2125 public boolean isOnCustomContent() {
2126 return mWorkspace.isOnOrMovingToCustomContent();
2127 }
2128
2129 @Override
2130 public boolean onPrepareOptionsMenu(Menu menu) {
2131 super.onPrepareOptionsMenu(menu);
2132 if (!isOnCustomContent()) {
2133 // Close any open folders
2134 closeFolder();
2135 // Stop resizing any widgets
2136 mWorkspace.exitWidgetResizeMode();
2137 if (!mWorkspace.isInOverviewMode()) {
2138 // Show the overview mode
2139 showOverviewMode(true);
2140 } else {
2141 showWorkspace(true);
2142 }
2143 }
2144 if (mLauncherCallbacks != null) {
2145 return mLauncherCallbacks.onPrepareOptionsMenu(menu);
2146 }
2147
2148 return false;
2149 }
2150
2151 @Override
2152 public boolean onSearchRequested() {
2153 startSearch(null, false, null, true);
2154 // Use a custom animation for launching search
2155 return true;
2156 }
2157
2158 public boolean isWorkspaceLocked() {
2159 return mWorkspaceLoading || mWaitingForResult;
2160 }
2161
2162 public boolean isWorkspaceLoading() {
2163 return mWorkspaceLoading;
2164 }
2165
2166 private void setWorkspaceLoading(boolean value) {
2167 boolean isLocked = isWorkspaceLocked();
2168 mWorkspaceLoading = value;
2169 if (isLocked != isWorkspaceLocked()) {
2170 onWorkspaceLockedChanged();
2171 }
2172 }
2173
2174 private void setWaitingForResult(boolean value) {
2175 boolean isLocked = isWorkspaceLocked();
2176 mWaitingForResult = value;
2177 if (isLocked != isWorkspaceLocked()) {
2178 onWorkspaceLockedChanged();
2179 }
2180 }
2181
2182 protected void onWorkspaceLockedChanged() {
2183 if (mLauncherCallbacks != null) {
2184 mLauncherCallbacks.onWorkspaceLockedChanged();
2185 }
2186 }
2187
2188 private void resetAddInfo() {
2189 mPendingAddInfo.container = ItemInfo.NO_ID;
2190 mPendingAddInfo.screenId = -1;
2191 mPendingAddInfo.cellX = mPendingAddInfo.cellY = -1;
2192 mPendingAddInfo.spanX = mPendingAddInfo.spanY = -1;
2193 mPendingAddInfo.minSpanX = mPendingAddInfo.minSpanY = -1;
2194 mPendingAddInfo.dropPos = null;
2195 }
2196
2197 void addAppWidgetImpl(final int appWidgetId, final ItemInfo info, final
2198 AppWidgetHostView boundWidget, final LauncherAppWidgetProviderInfo appWidgetInfo) {
2199 addAppWidgetImpl(appWidgetId, info, boundWidget, appWidgetInfo, 0);
2200 }
2201
2202 void addAppWidgetImpl(final int appWidgetId, final ItemInfo info,
2203 final AppWidgetHostView boundWidget, final LauncherAppWidgetProviderInfo appWidgetInfo,
2204 int delay) {
2205 if (appWidgetInfo.configure != null) {
2206 mPendingAddWidgetInfo = appWidgetInfo;
2207 mPendingAddWidgetId = appWidgetId;
2208
2209 // Launch over to configure widget, if needed
2210 mAppWidgetManager.startConfigActivity(appWidgetInfo, appWidgetId, this,
2211 mAppWidgetHost, REQUEST_CREATE_APPWIDGET);
2212
2213 } else {
2214 // Otherwise just add it
2215 Runnable onComplete = new Runnable() {
2216 @Override
2217 public void run() {
2218 // Exit spring loaded mode if necessary after adding the widget
2219 exitSpringLoadedDragModeDelayed(true, EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT,
2220 null);
2221 }
2222 };
2223 completeAddAppWidget(appWidgetId, info.container, info.screenId, boundWidget,
2224 appWidgetInfo);
2225 mWorkspace.removeExtraEmptyScreenDelayed(true, onComplete, delay, false);
2226 }
2227 }
2228
2229 protected void moveToCustomContentScreen(boolean animate) {
2230 // Close any folders that may be open.
2231 closeFolder();
2232 mWorkspace.moveToCustomContentScreen(animate);
2233 }
2234
2235 public void addPendingItem(PendingAddItemInfo info, long container, long screenId, int[] cell, int sp🔵
2236 switch (info.itemType) {
2237 case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET :
2238 case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET :
2239 int[] span = new int[2];
2240 span[0] = spanX;
2241 span[1] = spanY;
2242 addAppWidgetFromDrop(((PendingAddWidgetInfo) (info)), container, screenId, cell, span);
2243 break;
2244 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT :
2245 processShortcutFromDrop(info.componentName, container, screenId, cell);
2246 break;
2247 default :
2248 throw new IllegalStateException("Unknown item type: " + info.itemType);
2249 }
2250 }
2251
2252 /**
2253 * Process a shortcut drop.
2254 *
2255 * @param componentName The name of the component
2256 * @param screenId The ID of the screen where it should be added
2257 * @param cell The cell it should be added to, optional
2258 */
2259 private void processShortcutFromDrop(ComponentName componentName, long container, long screenId,
2260 int[] cell) {
2261 resetAddInfo();
2262 mPendingAddInfo.container = container;
2263 mPendingAddInfo.screenId = screenId;
2264 mPendingAddInfo.dropPos = null;
2265
2266 if (cell != null) {
2267 mPendingAddInfo.cellX = cell[0];
2268 mPendingAddInfo.cellY = cell[1];
2269 }
2270
2271 Intent createShortcutIntent = new Intent(Intent.ACTION_CREATE_SHORTCUT);
2272 createShortcutIntent.setComponent(componentName);
2273 processShortcut(createShortcutIntent);
2274 }
2275
2276 /**
2277 * Process a widget drop.
2278 *
2279 * @param info The PendingAppWidgetInfo of the widget being added.
2280 * @param screenId The ID of the screen where it should be added
2281 * @param cell The cell it should be added to, optional
2282 */
2283 private void addAppWidgetFromDrop(PendingAddWidgetInfo info, long container, long screenId, int[] cel🔵
2284 resetAddInfo();
2285 mPendingAddInfo.container = info.container = container;
2286 mPendingAddInfo.screenId = info.screenId = screenId;
2287 mPendingAddInfo.dropPos = null;
2288 mPendingAddInfo.minSpanX = info.minSpanX;
2289 mPendingAddInfo.minSpanY = info.minSpanY;
2290 if (cell != null) {
2291 mPendingAddInfo.cellX = cell[0];
2292 mPendingAddInfo.cellY = cell[1];
2293 }
2294 if (span != null) {
2295 mPendingAddInfo.spanX = span[0];
2296 mPendingAddInfo.spanY = span[1];
2297 }
2298 AppWidgetHostView hostView = info.boundWidget;
2299 int appWidgetId;
2300 if (hostView != null) {
2301 appWidgetId = hostView.getAppWidgetId();
2302 addAppWidgetImpl(appWidgetId, info, hostView, info.info);
2303 } else {
2304 // In this case, we either need to start an activity to get permission to bind
2305 // the widget, or we need to start an activity to configure the widget, or both.
2306 appWidgetId = getAppWidgetHost().allocateAppWidgetId();
2307 Bundle options = info.bindOptions;
2308 boolean success = mAppWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId, info.info, options)🔵
2309 if (success) {
2310 addAppWidgetImpl(appWidgetId, info, null, info.info);
2311 } else {
2312 mPendingAddWidgetInfo = info.info;
2313 Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_BIND);
2314 intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
2315 intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER, info.componentName);
2316 mAppWidgetManager.getUser(mPendingAddWidgetInfo).addToIntent(intent, AppWidgetManager.EXT🔵
2317 // TODO: we need to make sure that this accounts for the options bundle.
2318 // intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_OPTIONS, options);
2319 startActivityForResult(intent, REQUEST_BIND_APPWIDGET);
2320 }
2321 }
2322 }
2323
2324 void processShortcut(Intent intent) {
2325 Utilities.startActivityForResultSafely(this, intent, REQUEST_CREATE_SHORTCUT);
2326 }
2327
2328 void processWallpaper(Intent intent) {
2329 startActivityForResult(intent, REQUEST_PICK_WALLPAPER);
2330 }
2331
2332 FolderIcon addFolder(CellLayout layout, long container, final long screenId, int cellX, int cellY) {
2333 final FolderInfo folderInfo = new FolderInfo();
2334 folderInfo.title = getText(R.string.folder_name);
2335 // Update the model
2336 LauncherModel.addItemToDatabase(this, folderInfo, container, screenId, cellX, cellY);
2337 sFolders.put(folderInfo.id, folderInfo);
2338 // Create the view
2339 FolderIcon newFolder = FolderIcon.fromXml(R.layout.folder_icon, this, layout, folderInfo, mIconCa🔵
2340 mWorkspace.addInScreen(newFolder, container, screenId, cellX, cellY, 1, 1, isWorkspaceLocked());
2341 // Force measure the new folder icon
2342 CellLayout parent = mWorkspace.getParentCellLayoutForView(newFolder);
2343 parent.getShortcutsAndWidgets().measureChild(newFolder);
2344 return newFolder;
2345 }
2346
2347 void removeFolder(FolderInfo folder) {
2348 sFolders.remove(folder.id);
2349 }
2350
2351 /**
2352 * Registers various content observers. The current implementation registers
2353 * only a favorites observer to keep track of the favorites applications.
2354 */
2355 private void registerContentObservers() {
2356 ContentResolver resolver = getContentResolver();
2357 resolver.registerContentObserver(LauncherProvider.CONTENT_APPWIDGET_RESET_URI,
2358 true, mWidgetObserver);
2359 }
2360
2361 @Override
2362 public boolean dispatchKeyEvent(KeyEvent event) {
2363 if (event.getAction() == KeyEvent.ACTION_DOWN) {
2364 switch (event.getKeyCode()) {
2365 case KeyEvent.KEYCODE_HOME:
2366 return true;
2367 case KeyEvent.KEYCODE_VOLUME_DOWN:
2368 if (Utilities.isPropertyEnabled(DUMP_STATE_PROPERTY)) {
2369 dumpState();
2370 return true;
2371 }
2372 break;
2373 }
2374 } else if (event.getAction() == KeyEvent.ACTION_UP) {
2375 switch (event.getKeyCode()) {
2376 case KeyEvent.KEYCODE_HOME:
2377 return true;
2378 }
2379 }
2380
2381 return super.dispatchKeyEvent(event);
2382 }
2383
2384 @Override
2385 public void onBackPressed() {
2386 if ((mLauncherCallbacks != null) && mLauncherCallbacks.handleBackPressed()) {
2387 return;
2388 }
2389 LauncherAccessibilityDelegate delegate = LauncherAppState.getInstance().getAccessibilityDelegate(🔵
2390 if ((delegate != null) && delegate.onBackPressed()) {
2391 return;
2392 }
2393 if (isAppsViewVisible()) {
2394 showWorkspace(true);
2395 } else if (isWidgetsViewVisible()) {
2396 showOverviewMode(true);
2397 } else if (mWorkspace.isInOverviewMode()) {
2398 showWorkspace(true);
2399 } else if (mWorkspace.getOpenFolder() != null) {
2400 Folder openFolder = mWorkspace.getOpenFolder();
2401 if (openFolder.isEditingName()) {
2402 openFolder.dismissEditingName();
2403 } else {
2404 closeFolder();
2405 }
2406 } else {
2407 mWorkspace.exitWidgetResizeMode();
2408 // Back button is a no-op here, but give at least some feedback for the button press
2409 mWorkspace.showOutlinesTemporarily();
2410 }
2411 }
2412
2413 /**
2414 * Re-listen when widgets are reset.
2415 */
2416 @Thunk
2417 void onAppWidgetReset() {
2418 if (mAppWidgetHost != null) {
2419 mAppWidgetHost.startListening();
2420 }
2421 }
2422
2423 /**
2424 * Launches the intent referred by the clicked shortcut.
2425 *
2426 * @param v The view representing the clicked shortcut.
2427 */
2428 public void onClick(View v) {
2429 // Make sure that rogue clicks don't get through while allapps is launching, or after the
2430 // view has detached (it's possible for this to happen if the view is removed mid touch).
2431 if (v.getWindowToken() == null) {
2432 return;
2433 }
2434 if (!mWorkspace.isFinishedSwitchingState()) {
2435 return;
2436 }
2437 if (v instanceof Workspace) {
2438 if (mWorkspace.isInOverviewMode()) {
2439 showWorkspace(true);
2440 }
2441 return;
2442 }
2443 if (v instanceof CellLayout) {
2444 if (mWorkspace.isInOverviewMode()) {
2445 showWorkspace(mWorkspace.indexOfChild(v), true);
2446 }
2447 }
2448 Object tag = v.getTag();
2449 if (tag instanceof ShortcutInfo) {
2450 onClickAppShortcut(v);
2451 } else if (tag instanceof FolderInfo) {
2452 if (v instanceof FolderIcon) {
2453 onClickFolderIcon(v);
2454 }
2455 } else if (v == mAllAppsButton) {
2456 onClickAllAppsButton(v);
2457 } else if (tag instanceof AppInfo) {
2458 startAppShortcutOrInfoActivity(v);
2459 } else if (tag instanceof LauncherAppWidgetInfo) {
2460 if (v instanceof PendingAppWidgetHostView) {
2461 onClickPendingWidget(((PendingAppWidgetHostView) (v)));
2462 }
2463 }
2464 }
2465
2466 public void onClickPagedViewIcon(View v) {
2467 startAppShortcutOrInfoActivity(v);
2468 if (mLauncherCallbacks != null) {
2469 mLauncherCallbacks.onClickPagedViewIcon(v);
2470 }
2471 }
2472
2473 @SuppressLint("ClickableViewAccessibility")
2474 public boolean onTouch(View v, MotionEvent event) {
2475 return false;
2476 }
2477
2478 /**
2479 * Event handler for the app widget view which has not fully restored.
2480 */
2481 public void onClickPendingWidget(final PendingAppWidgetHostView v) {
2482 if (mIsSafeModeEnabled) {
2483 Toast.makeText(this, R.string.safemode_widget_error, Toast.LENGTH_SHORT).show();
2484 return;
2485 }
2486
2487 final LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) v.getTag();
2488 if (v.isReadyForClickSetup()) {
2489 int widgetId = info.appWidgetId;
2490 AppWidgetProviderInfo appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(widgetId);
2491 if (appWidgetInfo != null) {
2492 mPendingAddWidgetInfo = LauncherAppWidgetProviderInfo.fromProviderInfo(
2493 this, appWidgetInfo);
2494 mPendingAddInfo.copyFrom(info);
2495 mPendingAddWidgetId = widgetId;
2496
2497 AppWidgetManagerCompat.getInstance(this).startConfigActivity(appWidgetInfo,
2498 info.appWidgetId, this, mAppWidgetHost, REQUEST_RECONFIGURE_APPWIDGET);
2499 }
2500 } else if (info.installProgress < 0) {
2501 // The install has not been queued
2502 final String packageName = info.providerName.getPackageName();
2503 showBrokenAppInstallDialog(packageName,
2504 new DialogInterface.OnClickListener() {
2505 public void onClick(DialogInterface dialog, int id) {
2506 startActivitySafely(v, LauncherModel.getMarketIntent(packageName), info);
2507 }
2508 });
2509 } else {
2510 // Download has started.
2511 final String packageName = info.providerName.getPackageName();
2512 startActivitySafely(v, LauncherModel.getMarketIntent(packageName), info);
2513 }
2514 }
2515
2516 /**
2517 * Event handler for the "grid" button that appears on the home screen, which
2518 * enters all apps mode.
2519 *
2520 * @param v The view that was clicked.
2521 */
2522 protected void onClickAllAppsButton(View v) {
2523 if (LOGD) {
2524 Log.d(TAG, "onClickAllAppsButton");
2525 }
2526 if (isAppsViewVisible()) {
2527 showWorkspace(true);
2528 } else {
2529 // Try and refresh the set of predicted apps before we enter launcher
2530 /* animated */
2531 /* resetListToTop */
2532 /* updatePredictedApps */
2533 showAppsView(true, false, true);
2534 }
2535 }
2536
2537 private void showBrokenAppInstallDialog(final String packageName,
2538 DialogInterface.OnClickListener onSearchClickListener) {
2539 new AlertDialog.Builder(this)
2540 .setTitle(R.string.abandoned_promises_title)
2541 .setMessage(R.string.abandoned_promise_explanation)
2542 .setPositiveButton(R.string.abandoned_search, onSearchClickListener)
2543 .setNeutralButton(R.string.abandoned_clean_this,
2544 new DialogInterface.OnClickListener() {
2545 public void onClick(DialogInterface dialog, int id) {
2546 final UserHandleCompat user = UserHandleCompat.myUserHandle();
2547 mWorkspace.removeAbandonedPromise(packageName, user);
2548 }
2549 })
2550 .create().show();
2551 return;
2552 }
2553
2554 /**
2555 * Event handler for an app shortcut click.
2556 *
2557 * @param v The view that was clicked. Must be a tagged with a {@link ShortcutInfo}.
2558 */
2559 protected void onClickAppShortcut(final View v) {
2560 if (LOGD) Log.d(TAG, "onClickAppShortcut");
2561 Object tag = v.getTag();
2562 if (!(tag instanceof ShortcutInfo)) {
2563 throw new IllegalArgumentException("Input must be a Shortcut");
2564 }
2565
2566 // Open shortcut
2567 final ShortcutInfo shortcut = (ShortcutInfo) tag;
2568
2569 if (shortcut.isDisabled != 0) {
2570 int error = R.string.activity_not_available;
2571 if ((shortcut.isDisabled & ShortcutInfo.FLAG_DISABLED_SAFEMODE) != 0) {
2572 error = R.string.safemode_shortcut_error;
2573 }
2574 Toast.makeText(this, error, Toast.LENGTH_SHORT).show();
2575 return;
2576 }
2577
2578 final Intent intent = shortcut.intent;
2579
2580 // Check for special shortcuts
2581 if (intent.getComponent() != null) {
2582 final String shortcutClass = intent.getComponent().getClassName();
2583
2584 if (shortcutClass.equals(MemoryDumpActivity.class.getName())) {
2585 MemoryDumpActivity.startDump(this);
2586 return;
2587 } else if (shortcutClass.equals(ToggleWeightWatcher.class.getName())) {
2588 toggleShowWeightWatcher();
2589 return;
2590 }
2591 }
2592
2593 // Check for abandoned promise
2594 if ((v instanceof BubbleTextView)
2595 && shortcut.isPromise()
2596 && !shortcut.hasStatusFlag(ShortcutInfo.FLAG_INSTALL_SESSION_ACTIVE)) {
2597 showBrokenAppInstallDialog(
2598 shortcut.getTargetComponent().getPackageName(),
2599 new DialogInterface.OnClickListener() {
2600 public void onClick(DialogInterface dialog, int id) {
2601 startAppShortcutOrInfoActivity(v);
2602 }
2603 });
2604 return;
2605 }
2606
2607 // Start activities
2608 startAppShortcutOrInfoActivity(v);
2609
2610 if (mLauncherCallbacks != null) {
2611 mLauncherCallbacks.onClickAppShortcut(v);
2612 }
2613 }
2614
2615 @Thunk
2616 void startAppShortcutOrInfoActivity(View v) {
2617 Object tag = v.getTag();
2618 final ShortcutInfo shortcut;
2619 final Intent intent;
2620 if (tag instanceof ShortcutInfo) {
2621 shortcut = ((ShortcutInfo) (tag));
2622 intent = shortcut.intent;
2623 int[] pos = new int[2];
2624 v.getLocationOnScreen(pos);
2625 intent.setSourceBounds(new Rect(pos[0], pos[1], pos[0] + v.getWidth(), pos[1] + v.getHeight()🔵
2626 } else if (tag instanceof AppInfo) {
2627 shortcut = null;
2628 intent = ((AppInfo) (tag)).intent;
2629 } else {
2630 throw new IllegalArgumentException("Input must be a Shortcut or AppInfo");
2631 }
2632 boolean success = startActivitySafely(v, intent, tag);
2633 mStats.recordLaunch(intent, shortcut);
2634 if (success && (v instanceof BubbleTextView)) {
2635 mWaitingForResume = ((BubbleTextView) (v));
2636 mWaitingForResume.setStayPressed(true);
2637 }
2638 }
2639
2640 /**
2641 * Event handler for a folder icon click.
2642 *
2643 * @param v The view that was clicked. Must be an instance of {@link FolderIcon}.
2644 */
2645 protected void onClickFolderIcon(View v) {
2646 if (LOGD) Log.d(TAG, "onClickFolder");
2647 if (!(v instanceof FolderIcon)){
2648 throw new IllegalArgumentException("Input must be a FolderIcon");
2649 }
2650
2651 FolderIcon folderIcon = (FolderIcon) v;
2652 final FolderInfo info = folderIcon.getFolderInfo();
2653 Folder openFolder = mWorkspace.getFolderForTag(info);
2654
2655 // If the folder info reports that the associated folder is open, then verify that
2656 // it is actually opened. There have been a few instances where this gets out of sync.
2657 if (info.opened && openFolder == null) {
2658 Log.d(TAG, "Folder info marked as open, but associated folder is not open. Screen: "
2659 + info.screenId + " (" + info.cellX + ", " + info.cellY + ")");
2660 info.opened = false;
2661 }
2662
2663 if (!info.opened && !folderIcon.getFolder().isDestroyed()) {
2664 // Close any open folder
2665 closeFolder();
2666 // Open the requested folder
2667 openFolder(folderIcon);
2668 } else {
2669 // Find the open folder...
2670 int folderScreen;
2671 if (openFolder != null) {
2672 folderScreen = mWorkspace.getPageForView(openFolder);
2673 // .. and close it
2674 closeFolder(openFolder);
2675 if (folderScreen != mWorkspace.getCurrentPage()) {
2676 // Close any folder open on the current screen
2677 closeFolder();
2678 // Pull the folder onto this screen
2679 openFolder(folderIcon);
2680 }
2681 }
2682 }
2683
2684 if (mLauncherCallbacks != null) {
2685 mLauncherCallbacks.onClickFolderIcon(v);
2686 }
2687 }
2688
2689 /**
2690 * Event handler for the (Add) Widgets button that appears after a long press
2691 * on the home screen.
2692 */
2693 protected void onClickAddWidgetButton(View view) {
2694 if (LOGD) {
2695 Log.d(TAG, "onClickAddWidgetButton");
2696 }
2697 if (mIsSafeModeEnabled) {
2698 Toast.makeText(this, R.string.safemode_widget_error, Toast.LENGTH_SHORT).show();
2699 } else {
2700 /* animated */
2701 /* resetPageToZero */
2702 showWidgetsView(true, true);
2703 if (mLauncherCallbacks != null) {
2704 mLauncherCallbacks.onClickAddWidgetButton(view);
2705 }
2706 }
2707 }
2708
2709 /**
2710 * Event handler for the wallpaper picker button that appears after a long press
2711 * on the home screen.
2712 */
2713 protected void onClickWallpaperPicker(View v) {
2714 if (LOGD) {
2715 Log.d(TAG, "onClickWallpaperPicker");
2716 }
2717 startActivityForResult(new Intent(Intent.ACTION_SET_WALLPAPER).setPackage(getPackageName()), REQU🔵
2718 if (mLauncherCallbacks != null) {
2719 mLauncherCallbacks.onClickWallpaperPicker(v);
2720 }
2721 }
2722
2723 /**
2724 * Event handler for a click on the settings button that appears after a long press
2725 * on the home screen.
2726 */
2727 protected void onClickSettingsButton(View v) {
2728 if (LOGD) Log.d(TAG, "onClickSettingsButton");
2729 if (mLauncherCallbacks != null) {
2730 mLauncherCallbacks.onClickSettingsButton(v);
2731 }
2732 }
2733
2734 public void onTouchDownAllAppsButton(View v) {
2735 // Provide the same haptic feedback that the system offers for virtual keys.
2736 v.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
2737 }
2738
2739 public void performHapticFeedbackOnTouchDown(View v) {
2740 // Provide the same haptic feedback that the system offers for virtual keys.
2741 v.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
2742 }
2743
2744 public View.OnTouchListener getHapticFeedbackTouchListener() {
2745 if (mHapticFeedbackTouchListener == null) {
2746 mHapticFeedbackTouchListener = new View.OnTouchListener() {
2747 @SuppressLint("ClickableViewAccessibility")
2748 @Override
2749 public boolean onTouch(View v, MotionEvent event) {
2750 if ((event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_DOWN) {
2751 v.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
2752 }
2753 return false;
2754 }
2755 };
2756 }
2757 return mHapticFeedbackTouchListener;
2758 }
2759
2760 public void onDragStarted(View view) {
2761 if (isOnCustomContent()) {
2762 // Custom content screen doesn't participate in drag and drop. If on custom
2763 // content screen, move to default.
2764 moveWorkspaceToDefaultScreen();
2765 }
2766
2767 if (mLauncherCallbacks != null) {
2768 mLauncherCallbacks.onDragStarted(view);
2769 }
2770 }
2771
2772 /**
2773 * Called when the user stops interacting with the launcher.
2774 * This implies that the user is now on the homescreen and is not doing housekeeping.
2775 */
2776 protected void onInteractionEnd() {
2777 if (mLauncherCallbacks != null) {
2778 mLauncherCallbacks.onInteractionEnd();
2779 }
2780 }
2781
2782 /**
2783 * Called when the user starts interacting with the launcher.
2784 * The possible interactions are:
2785 * - open all apps
2786 * - reorder an app shortcut, or a widget
2787 * - open the overview mode.
2788 * This is a good time to stop doing things that only make sense
2789 * when the user is on the homescreen and not doing housekeeping.
2790 */
2791 protected void onInteractionBegin() {
2792 if (mLauncherCallbacks != null) {
2793 mLauncherCallbacks.onInteractionBegin();
2794 }
2795 }
2796
2797 /** Updates the interaction state. */
2798 public void updateInteraction(Workspace.State fromState, Workspace.State toState) {
2799 // Only update the interacting state if we are transitioning to/from a view with an
2800 // overlay
2801 boolean fromStateWithOverlay;
2802 boolean toStateWithOverlay;
2803 if (Launcher.DISABLE_ALL_APPS_SEARCH_INTEGRATION) {
2804 fromStateWithOverlay = fromState != Workspace.State.NORMAL;
2805 toStateWithOverlay = toState != Workspace.State.NORMAL;
2806 } else {
2807 fromStateWithOverlay = (fromState != Workspace.State.NORMAL) && (fromState != Workspace.State🔵
2808 toStateWithOverlay = (toState != Workspace.State.NORMAL) && (toState != Workspace.State.NORMA🔵
2809 }
2810 if (toStateWithOverlay) {
2811 onInteractionBegin();
2812 } else if (fromStateWithOverlay) {
2813 onInteractionEnd();
2814 }
2815 }
2816
2817 void startApplicationDetailsActivity(ComponentName componentName, UserHandleCompat user) {
2818 try {
2819 LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(this);
2820 launcherApps.showAppDetailsForProfile(componentName, user);
2821 } catch (SecurityException e) {
2822 Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
2823 Log.e(TAG, "Launcher does not have permission to launch settings");
2824 } catch (ActivityNotFoundException e) {
2825 Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
2826 Log.e(TAG, "Unable to launch settings");
2827 }
2828 }
2829
2830 // returns true if the activity was started
2831 boolean startApplicationUninstallActivity(ComponentName componentName, int flags,
2832 UserHandleCompat user) {
2833 if ((flags & AppInfo.DOWNLOADED_FLAG) == 0) {
2834 // System applications cannot be installed. For now, show a toast explaining that.
2835 // We may give them the option of disabling apps this way.
2836 int messageId = R.string.uninstall_system_app_text;
2837 Toast.makeText(this, messageId, Toast.LENGTH_SHORT).show();
2838 return false;
2839 } else {
2840 String packageName = componentName.getPackageName();
2841 String className = componentName.getClassName();
2842 Intent intent = new Intent(
2843 Intent.ACTION_DELETE, Uri.fromParts("package", packageName, className));
2844 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
2845 Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
2846 if (user != null) {
2847 user.addToIntent(intent, Intent.EXTRA_USER);
2848 }
2849 startActivity(intent);
2850 return true;
2851 }
2852 }
2853
2854 boolean startActivity(View v, Intent intent, Object tag) {
2855 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
2856 try {
2857 // Only launch using the new animation if the shortcut has not opted out (this is a
2858 // private contract between launcher and may be ignored in the future).
2859 boolean useLaunchAnimation = (v != null) && (!intent.hasExtra(INTENT_EXTRA_IGNORE_LAUNCH_ANIM🔵
2860 LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(this);
2861 UserManagerCompat userManager = UserManagerCompat.getInstance(this);
2862 UserHandleCompat user = null;
2863 if (intent.hasExtra(AppInfo.EXTRA_PROFILE)) {
2864 long serialNumber = intent.getLongExtra(AppInfo.EXTRA_PROFILE, -1);
2865 user = userManager.getUserForSerialNumber(serialNumber);
2866 }
2867 Bundle optsBundle = null;
2868 if (useLaunchAnimation) {
2869 ActivityOptions opts = null;
2870 if (sClipRevealMethod != null) {
2871 // TODO: call method directly when Launcher3 can depend on M APIs
2872 int left = 0;
2873 int top = 0;
2874 int width = v.getMeasuredWidth();
2875 int height = v.getMeasuredHeight();
2876 if (v instanceof TextView) {
2877 // Launch from center of icon, not entire view
2878 Drawable icon = Workspace.getTextViewIcon(((TextView) (v)));
2879 if (icon != null) {
2880 Rect bounds = icon.getBounds();
2881 left = (width - bounds.width()) / 2;
2882 top = v.getPaddingTop();
2883 width = bounds.width();
2884 height = bounds.height();
2885 }
2886 }
2887 try {
2888 opts = ((ActivityOptions) (sClipRevealMethod.invoke(null, v, left, top, width, he🔵
2889 } catch (java.lang.IllegalAccessException e) {
2890 Log.d(TAG, "Could not call makeClipRevealAnimation: " + e);
2891 sClipRevealMethod = null;
2892 } catch (InvocationTargetException e) {
2893 Log.d(TAG, "Could not call makeClipRevealAnimation: " + e);
2894 sClipRevealMethod = null;
2895 }
2896 }
2897 if ((opts == null) && (!Utilities.isLmpOrAbove())) {
2898 opts = ActivityOptions.makeScaleUpAnimation(v, 0, 0, v.getMeasuredWidth(), v.getMeasu🔵
2899 }
2900 optsBundle = (opts != null) ? opts.toBundle() : null;
2901 }
2902 if ((user == null) || user.equals(UserHandleCompat.myUserHandle())) {
2903 // Could be launching some bookkeeping activity
2904 startActivity(intent, optsBundle);
2905 } else {
2906 // TODO Component can be null when shortcuts are supported for secondary user
2907 launcherApps.startActivityForProfile(intent.getComponent(), user, intent.getSourceBounds(🔵
2908 }
2909 return true;
2910 } catch (java.lang.SecurityException e) {
2911 Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
2912 Log.e(TAG, (((((("Launcher does not have the permission to launch " + intent) + ". Make sure 🔵
2913 }
2914 return false;
2915 }
2916
2917 boolean startActivitySafely(View v, Intent intent, Object tag) {
2918 boolean success = false;
2919 if (mIsSafeModeEnabled && !Utilities.isSystemApp(this, intent)) {
2920 Toast.makeText(this, R.string.safemode_shortcut_error, Toast.LENGTH_SHORT).show();
2921 return false;
2922 }
2923 try {
2924 success = startActivity(v, intent, tag);
2925 } catch (ActivityNotFoundException e) {
2926 Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
2927 Log.e(TAG, "Unable to launch. tag=" + tag + " intent=" + intent, e);
2928 }
2929 return success;
2930 }
2931
2932 /**
2933 * This method draws the FolderIcon to an ImageView and then adds and positions that ImageView
2934 * in the DragLayer in the exact absolute location of the original FolderIcon.
2935 */
2936 private void copyFolderIconToImage(FolderIcon fi) {
2937 final int width = fi.getMeasuredWidth();
2938 final int height = fi.getMeasuredHeight();
2939
2940 // Lazy load ImageView, Bitmap and Canvas
2941 if (mFolderIconImageView == null) {
2942 mFolderIconImageView = new ImageView(this);
2943 }
2944 if (mFolderIconBitmap == null || mFolderIconBitmap.getWidth() != width ||
2945 mFolderIconBitmap.getHeight() != height) {
2946 mFolderIconBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
2947 mFolderIconCanvas = new Canvas(mFolderIconBitmap);
2948 }
2949
2950 DragLayer.LayoutParams lp;
2951 if (mFolderIconImageView.getLayoutParams() instanceof DragLayer.LayoutParams) {
2952 lp = (DragLayer.LayoutParams) mFolderIconImageView.getLayoutParams();
2953 } else {
2954 lp = new DragLayer.LayoutParams(width, height);
2955 }
2956
2957 // The layout from which the folder is being opened may be scaled, adjust the starting
2958 // view size by this scale factor.
2959 float scale = mDragLayer.getDescendantRectRelativeToSelf(fi, mRectForFolderAnimation);
2960 lp.customPosition = true;
2961 lp.x = mRectForFolderAnimation.left;
2962 lp.y = mRectForFolderAnimation.top;
2963 lp.width = (int) (scale * width);
2964 lp.height = (int) (scale * height);
2965
2966 mFolderIconCanvas.drawColor(0, PorterDuff.Mode.CLEAR);
2967 fi.draw(mFolderIconCanvas);
2968 mFolderIconImageView.setImageBitmap(mFolderIconBitmap);
2969 if (fi.getFolder() != null) {
2970 mFolderIconImageView.setPivotX(fi.getFolder().getPivotXForIconAnimation());
2971 mFolderIconImageView.setPivotY(fi.getFolder().getPivotYForIconAnimation());
2972 }
2973 // Just in case this image view is still in the drag layer from a previous animation,
2974 // we remove it and re-add it.
2975 if (mDragLayer.indexOfChild(mFolderIconImageView) != -1) {
2976 mDragLayer.removeView(mFolderIconImageView);
2977 }
2978 mDragLayer.addView(mFolderIconImageView, lp);
2979 if (fi.getFolder() != null) {
2980 fi.getFolder().bringToFront();
2981 }
2982 }
2983
2984 private void growAndFadeOutFolderIcon(FolderIcon fi) {
2985 if (fi == null) return;
2986 PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 0);
2987 PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 1.5f);
2988 PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 1.5f);
2989
2990 FolderInfo info = (FolderInfo) fi.getTag();
2991 if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
2992 CellLayout cl = (CellLayout) fi.getParent().getParent();
2993 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) fi.getLayoutParams();
2994 cl.setFolderLeaveBehindCell(lp.cellX, lp.cellY);
2995 }
2996
2997 // Push an ImageView copy of the FolderIcon into the DragLayer and hide the original
2998 copyFolderIconToImage(fi);
2999 fi.setVisibility(View.INVISIBLE);
3000
3001 ObjectAnimator oa = LauncherAnimUtils.ofPropertyValuesHolder(mFolderIconImageView, alpha,
3002 scaleX, scaleY);
3003 if (Utilities.isLmpOrAbove()) {
3004 oa.setInterpolator(new LogDecelerateInterpolator(100, 0));
3005 }
3006 oa.setDuration(getResources().getInteger(R.integer.config_folderExpandDuration));
3007 oa.start();
3008 }
3009
3010 private void shrinkAndFadeInFolderIcon(final FolderIcon fi) {
3011 if (fi == null) return;
3012 PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 1.0f);
3013 PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 1.0f);
3014 PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 1.0f);
3015
3016 final CellLayout cl = (CellLayout) fi.getParent().getParent();
3017
3018 // We remove and re-draw the FolderIcon in-case it has changed
3019 mDragLayer.removeView(mFolderIconImageView);
3020 copyFolderIconToImage(fi);
3021 ObjectAnimator oa = LauncherAnimUtils.ofPropertyValuesHolder(mFolderIconImageView, alpha,
3022 scaleX, scaleY);
3023 oa.setDuration(getResources().getInteger(R.integer.config_folderExpandDuration));
3024 oa.addListener(new AnimatorListenerAdapter() {
3025 @Override
3026 public void onAnimationEnd(Animator animation) {
3027 if (cl != null) {
3028 cl.clearFolderLeaveBehind();
3029 // Remove the ImageView copy of the FolderIcon and make the original visible.
3030 mDragLayer.removeView(mFolderIconImageView);
3031 fi.setVisibility(View.VISIBLE);
3032 }
3033 }
3034 });
3035 oa.start();
3036 }
3037
3038 /**
3039 * Opens the user folder described by the specified tag. The opening of the folder
3040 * is animated relative to the specified View. If the View is null, no animation
3041 * is played.
3042 *
3043 * @param folderInfo The FolderInfo describing the folder to open.
3044 */
3045 public void openFolder(FolderIcon folderIcon) {
3046 Folder folder = folderIcon.getFolder();
3047 Folder openFolder = (mWorkspace != null) ? mWorkspace.getOpenFolder() : null;
3048 if ((openFolder != null) && (openFolder != folder)) {
3049 // Close any open folder before opening a folder.
3050 closeFolder();
3051 }
3052 FolderInfo info = folder.mInfo;
3053 info.opened = true;
3054 // While the folder is open, the position of the icon cannot change.
3055 ((CellLayout.LayoutParams) (folderIcon.getLayoutParams())).canReorder = false;
3056 // Just verify that the folder hasn't already been added to the DragLayer.
3057 // There was a one-off crash where the folder had a parent already.
3058 if (folder.getParent() == null) {
3059 mDragLayer.addView(folder);
3060 mDragController.addDropTarget(((DropTarget) (folder)));
3061 } else {
3062 Log.w(TAG, ((("Opening folder (" + folder) + ") which already has a parent (") + folder.getPa🔵
3063 }
3064 folder.animateOpen();
3065 growAndFadeOutFolderIcon(folderIcon);
3066 // Notify the accessibility manager that this folder "window" has appeared and occluded
3067 // the workspace items
3068 folder.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
3069 getDragLayer().sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
3070 }
3071
3072 public void closeFolder() {
3073 Folder folder = mWorkspace != null ? mWorkspace.getOpenFolder() : null;
3074 if (folder != null) {
3075 if (folder.isEditingName()) {
3076 folder.dismissEditingName();
3077 }
3078 closeFolder(folder);
3079 }
3080 }
3081
3082 public void closeFolder(Folder folder) {
3083 folder.getInfo().opened = false;
3084 ViewGroup parent = ((ViewGroup) (folder.getParent().getParent()));
3085 if (parent != null) {
3086 FolderIcon fi = ((FolderIcon) (mWorkspace.getViewForTag(folder.mInfo)));
3087 shrinkAndFadeInFolderIcon(fi);
3088 if (fi != null) {
3089 ((CellLayout.LayoutParams) (fi.getLayoutParams())).canReorder = true;
3090 }
3091 }
3092 folder.animateClosed();
3093 // Notify the accessibility manager that this folder "window" has disappeard and no
3094 // longer occludeds the workspace items
3095 getDragLayer().sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
3096 }
3097
3098 public boolean onLongClick(View v) {
3099 if (!isDraggingEnabled()) {
3100 return false;
3101 }
3102 if (isWorkspaceLocked()) {
3103 return false;
3104 }
3105 if (mState != State.WORKSPACE) {
3106 return false;
3107 }
3108 if (v instanceof Workspace) {
3109 if (!mWorkspace.isInOverviewMode()) {
3110 if (!mWorkspace.isTouchActive()) {
3111 showOverviewMode(true);
3112 mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, HapticFeedbackCo🔵
3113 return true;
3114 } else {
3115 return false;
3116 }
3117 } else {
3118 return false;
3119 }
3120 }
3121 CellLayout.CellInfo longClickCellInfo = null;
3122 View itemUnderLongClick = null;
3123 if (v.getTag() instanceof ItemInfo) {
3124 ItemInfo info = ((ItemInfo) (v.getTag()));
3125 longClickCellInfo = new CellLayout.CellInfo(v, info);
3126 itemUnderLongClick = longClickCellInfo.cell;
3127 resetAddInfo();
3128 }
3129 // The hotseat touch handling does not go through Workspace, and we always allow long press
3130 // on hotseat items.
3131 final boolean inHotseat = isHotseatLayout(v);
3132 if (!mDragController.isDragging()) {
3133 if (itemUnderLongClick == null) {
3134 // User long pressed on empty space
3135 mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, HapticFeedbackConsta🔵
3136 if (mWorkspace.isInOverviewMode()) {
3137 mWorkspace.startReordering(v);
3138 } else {
3139 showOverviewMode(true);
3140 }
3141 } else {
3142 final boolean isAllAppsButton = inHotseat && isAllAppsButtonRank(mHotseat.getOrderInHotse🔵
3143 if (!((itemUnderLongClick instanceof Folder) || isAllAppsButton)) {
3144 // User long pressed on an item
3145 mWorkspace.startDrag(longClickCellInfo);
3146 }
3147 }
3148 }
3149 return true;
3150 }
3151
3152 boolean isHotseatLayout(View layout) {
3153 return mHotseat != null && layout != null &&
3154 (layout instanceof CellLayout) && (layout == mHotseat.getLayout());
3155 }
3156
3157 /**
3158 * Returns the CellLayout of the specified container at the specified screen.
3159 */
3160 public CellLayout getCellLayout(long container, long screenId) {
3161 if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
3162 if (mHotseat != null) {
3163 return mHotseat.getLayout();
3164 } else {
3165 return null;
3166 }
3167 } else {
3168 return mWorkspace.getScreenWithId(screenId);
3169 }
3170 }
3171
3172 /**
3173 * For overridden classes.
3174 */
3175 public boolean isAllAppsVisible() {
3176 return isAppsViewVisible();
3177 }
3178
3179 public boolean isAppsViewVisible() {
3180 return (mState == State.APPS) || (mOnResumeState == State.APPS);
3181 }
3182
3183 public boolean isWidgetsViewVisible() {
3184 return (mState == State.WIDGETS) || (mOnResumeState == State.WIDGETS);
3185 }
3186
3187 private void setWorkspaceBackground(int background) {
3188 switch (background) {
3189 case WORKSPACE_BACKGROUND_TRANSPARENT :
3190 getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
3191 break;
3192 case WORKSPACE_BACKGROUND_BLACK :
3193 getWindow().setBackgroundDrawable(null);
3194 break;
3195 default :
3196 getWindow().setBackgroundDrawable(mWorkspaceBackgroundDrawable);
3197 }
3198 }
3199
3200 protected void changeWallpaperVisiblity(boolean visible) {
3201 int wpflags = (visible) ? WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER : 0;
3202 int curflags = getWindow().getAttributes().flags & WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER🔵
3203 if (wpflags != curflags) {
3204 getWindow().setFlags(wpflags, WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER);
3205 }
3206 setWorkspaceBackground(visible ? WORKSPACE_BACKGROUND_GRADIENT : WORKSPACE_BACKGROUND_BLACK);
3207 }
3208
3209 @Override
3210 public void onTrimMemory(int level) {
3211 super.onTrimMemory(level);
3212 if (level >= ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
3213 // The widget preview db can result in holding onto over
3214 // 3MB of memory for caching which isn't necessary.
3215 SQLiteDatabase.releaseMemory();
3216 // This clears all widget bitmaps from the widget tray
3217 // TODO(hyunyoungs)
3218 }
3219 if (mLauncherCallbacks != null) {
3220 mLauncherCallbacks.onTrimMemory(level);
3221 }
3222 }
3223
3224 @Override
3225 public void onStateTransitionHideSearchBar() {
3226 // Hide the search bar
3227 if (mSearchDropTargetBar != null) {
3228 /* animated */
3229 mSearchDropTargetBar.hideSearchBar(false);
3230 }
3231 }
3232
3233 public void showWorkspace(boolean animated) {
3234 showWorkspace(WorkspaceStateTransitionAnimation.SCROLL_TO_CURRENT_PAGE, animated, null, true);
3235 }
3236
3237 public void showWorkspace(boolean animated, Runnable onCompleteRunnable) {
3238 showWorkspace(WorkspaceStateTransitionAnimation.SCROLL_TO_CURRENT_PAGE, animated, onCompleteRunna🔵
3239 }
3240
3241 protected void showWorkspace(int snapToPage, boolean animated) {
3242 showWorkspace(snapToPage, animated, null, true);
3243 }
3244
3245 void showWorkspace(int snapToPage, boolean animated, Runnable onCompleteRunnable, boolean notifyLaunc🔵
3246 boolean changed = (mState != State.WORKSPACE) || (mWorkspace.getState() != Workspace.State.NORMAL🔵
3247 if (changed) {
3248 boolean wasInSpringLoadedMode = mState != State.WORKSPACE;
3249 mWorkspace.setVisibility(View.VISIBLE);
3250 mStateTransitionAnimation.startAnimationToWorkspace(mState, Workspace.State.NORMAL, snapToPag🔵
3251 // Show the search bar (only animate if we were showing the drop target bar in spring
3252 // loaded mode)
3253 if (mSearchDropTargetBar != null) {
3254 mSearchDropTargetBar.showSearchBar(animated && wasInSpringLoadedMode);
3255 }
3256 // Set focus to the AppsCustomize button
3257 if (mAllAppsButton != null) {
3258 mAllAppsButton.requestFocus();
3259 }
3260 }
3261 // Change the state *after* we've called all the transition code
3262 mState = State.WORKSPACE;
3263 // Resume the auto-advance of widgets
3264 mUserPresent = true;
3265 updateAutoAdvanceState();
3266 if (changed) {
3267 // Send an accessibility event to announce the context change
3268 getWindow().getDecorView().sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGE🔵
3269 if (notifyLauncherCallbacks) {
3270 // Dismiss all apps when the workspace is shown
3271 if ((!Launcher.DISABLE_ALL_APPS_SEARCH_INTEGRATION) && (mLauncherCallbacks != null)) {
3272 mLauncherCallbacks.onAllAppsHidden();
3273 }
3274 }
3275 }
3276 }
3277
3278 void showOverviewMode(boolean animated) {
3279 mWorkspace.setVisibility(View.VISIBLE);
3280 /* onCompleteRunnable */
3281 mStateTransitionAnimation.startAnimationToWorkspace(mState, Workspace.State.OVERVIEW, WorkspaceSt🔵
3282 mState = State.WORKSPACE;
3283 }
3284
3285 /**
3286 * Shows the apps view.
3287 */
3288 void showAppsView(boolean animated, boolean resetListToTop, boolean updatePredictedApps) {
3289 if (resetListToTop) {
3290 mAppsView.scrollToTop();
3291 }
3292 if (updatePredictedApps) {
3293 tryAndUpdatePredictedApps();
3294 }
3295 showAppsOrWidgets(animated, State.APPS);
3296 }
3297
3298 /**
3299 * Shows the widgets view.
3300 */
3301 void showWidgetsView(boolean animated, boolean resetPageToZero) {
3302 if (LOGD) {
3303 Log.d(TAG, (("showWidgetsView:" + animated) + " resetPageToZero:") + resetPageToZero);
3304 }
3305 if (resetPageToZero) {
3306 mWidgetsView.scrollToTop();
3307 }
3308 showAppsOrWidgets(animated, State.WIDGETS);
3309 mWidgetsView.post(new Runnable() {
3310 @Override
3311 public void run() {
3312 mWidgetsView.requestFocus();
3313 }
3314 });
3315 }
3316
3317 /**
3318 * Sets up the transition to show the apps/widgets view.
3319 *
3320 * @return whether the current from and to state allowed this operation
3321 */
3322 // TODO: calling method should use the return value so that when {@code false} is returned
3323 // the workspace transition doesn't fall into invalid state.
3324 private boolean showAppsOrWidgets(boolean animated, State toState) {
3325 if (((mState != State.WORKSPACE) && (mState != State.APPS_SPRING_LOADED)) && (mState != State.WID🔵
3326 return false;
3327 }
3328 if ((toState != State.APPS) && (toState != State.WIDGETS)) {
3329 return false;
3330 }
3331 if (toState == State.APPS) {
3332 mStateTransitionAnimation.startAnimationToAllApps(animated);
3333 if ((!Launcher.DISABLE_ALL_APPS_SEARCH_INTEGRATION) && (mLauncherCallbacks != null)) {
3334 mLauncherCallbacks.onAllAppsShown();
3335 }
3336 } else {
3337 mStateTransitionAnimation.startAnimationToWidgets(animated);
3338 }
3339 // Change the state *after* we've called all the transition code
3340 mState = toState;
3341 // Pause the auto-advance of widgets until we are out of AllApps
3342 mUserPresent = false;
3343 updateAutoAdvanceState();
3344 closeFolder();
3345 // Send an accessibility event to announce the context change
3346 getWindow().getDecorView().sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
3347 return true;
3348 }
3349
3350 /**
3351 * Updates the workspace and interaction state on state change, and return the animation to this
3352 * new state.
3353 */
3354 public Animator startWorkspaceStateChangeAnimation(Workspace.State toState, int toPage, boolean anima🔵
3355 Workspace.State fromState = mWorkspace.getState();
3356 Animator anim = mWorkspace.setStateWithAnimation(toState, toPage, animated, layerViews);
3357 updateInteraction(fromState, toState);
3358 return anim;
3359 }
3360
3361 public void enterSpringLoadedDragMode() {
3362 if (LOGD) {
3363 Log.d(TAG, String.format("enterSpringLoadedDragMode [mState=%s", mState.name()));
3364 }
3365 if (((mState == State.WORKSPACE) || (mState == State.APPS_SPRING_LOADED)) || (mState == State.WID🔵
3366 return;
3367 }
3368 /* animated */
3369 /* onCompleteRunnable */
3370 mStateTransitionAnimation.startAnimationToWorkspace(mState, Workspace.State.SPRING_LOADED, Worksp🔵
3371 mState = (isAppsViewVisible()) ? State.APPS_SPRING_LOADED : State.WIDGETS_SPRING_LOADED;
3372 }
3373
3374 public void exitSpringLoadedDragModeDelayed(final boolean successfulDrop, int delay, final Runnable o🔵
3375 if ((mState != State.APPS_SPRING_LOADED) && (mState != State.WIDGETS_SPRING_LOADED)) {
3376 return;
3377 }
3378 if (successfulDrop) {
3379 // We need to trigger all apps hidden to notify search to update itself before the
3380 // delayed call to showWorkspace below
3381 if ((!Launcher.DISABLE_ALL_APPS_SEARCH_INTEGRATION) && (mLauncherCallbacks != null)) {
3382 mLauncherCallbacks.onAllAppsHidden();
3383 }
3384 }
3385 mHandler.postDelayed(new Runnable() {
3386 @Override
3387 public void run() {
3388 if (successfulDrop) {
3389 // TODO(hyunyoungs): verify if this hack is still needed, if not, delete.
3390 //
3391 // Before we show workspace, hide all apps again because
3392 // exitSpringLoadedDragMode made it visible. This is a bit hacky; we should
3393 // clean up our state transition functions
3394 mWidgetsView.setVisibility(View.GONE);
3395 showWorkspace(true, onCompleteRunnable);
3396 } else {
3397 exitSpringLoadedDragMode();
3398 }
3399 }
3400 }, delay);
3401 }
3402
3403 void exitSpringLoadedDragMode() {
3404 if (mState == State.APPS_SPRING_LOADED) {
3405 /* animated */
3406 /* resetListToTop */
3407 /* updatePredictedApps */
3408 showAppsView(true, false, false);
3409 } else if (mState == State.WIDGETS_SPRING_LOADED) {
3410 showWidgetsView(true, false);
3411 }
3412 }
3413
3414 /**
3415 * Updates the set of predicted apps if it hasn't been updated since the last time Launcher was
3416 * resumed.
3417 */
3418 private void tryAndUpdatePredictedApps() {
3419 if (mLauncherCallbacks != null) {
3420 List<ComponentName> apps = mLauncherCallbacks.getPredictedApps();
3421 if (!apps.isEmpty()) {
3422 mAppsView.setPredictedApps(apps);
3423 }
3424 }
3425 }
3426
3427 void lockAllApps() {
3428 // TODO
3429 }
3430
3431 void unlockAllApps() {
3432 // TODO
3433 }
3434
3435 protected void disableVoiceButtonProxy(boolean disable) {
3436 // NO-OP
3437 }
3438
3439 public View getOrCreateQsbBar() {
3440 if ((mLauncherCallbacks != null) && mLauncherCallbacks.providesSearch()) {
3441 return mLauncherCallbacks.getQsbBar();
3442 }
3443 if (mQsb == null) {
3444 AppWidgetProviderInfo searchProvider = Utilities.getSearchWidgetProvider(this);
3445 if (searchProvider == null) {
3446 return null;
3447 }
3448 Bundle opts = new Bundle();
3449 opts.putInt(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY, AppWidgetProviderInfo.WIDGET_CAT🔵
3450 SharedPreferences sp = getSharedPreferences(LauncherAppState.getSharedPreferencesKey(), MODE_🔵
3451 int widgetId = sp.getInt(QSB_WIDGET_ID, -1);
3452 AppWidgetProviderInfo widgetInfo = mAppWidgetManager.getAppWidgetInfo(widgetId);
3453 if (((!searchProvider.provider.flattenToString().equals(sp.getString(QSB_WIDGET_PROVIDER, nul🔵
3454 // A valid widget is not already bound.
3455 if (widgetId > (-1)) {
3456 mAppWidgetHost.deleteAppWidgetId(widgetId);
3457 widgetId = -1;
3458 }
3459 // Try to bind a new widget
3460 widgetId = mAppWidgetHost.allocateAppWidgetId();
3461 if (!AppWidgetManagerCompat.getInstance(this).bindAppWidgetIdIfAllowed(widgetId, searchPr🔵
3462 mAppWidgetHost.deleteAppWidgetId(widgetId);
3463 widgetId = -1;
3464 }
3465 sp.edit().putInt(QSB_WIDGET_ID, widgetId).putString(QSB_WIDGET_PROVIDER, searchProvider.p🔵
3466 }
3467 if (widgetId != (-1)) {
3468 mQsb = mAppWidgetHost.createView(this, widgetId, searchProvider);
3469 mQsb.updateAppWidgetOptions(opts);
3470 mQsb.setPadding(0, 0, 0, 0);
3471 mSearchDropTargetBar.addView(mQsb);
3472 mSearchDropTargetBar.setQsbSearchBar(mQsb);
3473 }
3474 }
3475 return mQsb;
3476 }
3477
3478 private void reinflateQSBIfNecessary() {
3479 if ((mQsb instanceof LauncherAppWidgetHostView) && ((LauncherAppWidgetHostView) (mQsb)).isReinfla🔵
3480 mSearchDropTargetBar.removeView(mQsb);
3481 mQsb = null;
3482 mSearchDropTargetBar.setQsbSearchBar(getOrCreateQsbBar());
3483 }
3484 }
3485
3486 @Override
3487 public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
3488 final boolean result = super.dispatchPopulateAccessibilityEvent(event);
3489 final List<CharSequence> text = event.getText();
3490 text.clear();
3491 // Populate event with a fake title based on the current state.
3492 if (mState == State.APPS) {
3493 text.add(getString(R.string.all_apps_button_label));
3494 } else if (mState == State.WIDGETS) {
3495 text.add(getString(R.string.widget_button_text));
3496 } else {
3497 text.add(getString(R.string.all_apps_home_button_label));
3498 }
3499 return result;
3500 }
3501
3502 /**
3503 * Receives notifications when system dialogs are to be closed.
3504 */
3505 @Thunk
3506 class CloseSystemDialogsIntentReceiver extends BroadcastReceiver {
3507 @Override
3508 public void onReceive(Context context, Intent intent) {
3509 closeSystemDialogs();
3510 }
3511 }
3512
3513 /**
3514 * Receives notifications whenever the appwidgets are reset.
3515 */
3516 private class AppWidgetResetObserver extends ContentObserver {
3517 public AppWidgetResetObserver() {
3518 super(new Handler());
3519 }
3520
3521 @Override
3522 public void onChange(boolean selfChange) {
3523 onAppWidgetReset();
3524 }
3525 }
3526
3527 /**
3528 * If the activity is currently paused, signal that we need to run the passed Runnable
3529 * in onResume.
3530 *
3531 * This needs to be called from incoming places where resources might have been loaded
3532 * while the activity is paused. That is because the Configuration (e.g., rotation) might be
3533 * wrong when we're not running, and if the activity comes back to what the configuration was
3534 * when we were paused, activity is not restarted.
3535 *
3536 * Implementation of the method from LauncherModel.Callbacks.
3537 *
3538 * @return true if we are currently paused. The caller might be able to
3539 skip some work in that case since we will come back again.
3540 */
3541 private boolean waitUntilResume(Runnable run, boolean deletePreviousRunnables) {
3542 if (mPaused) {
3543 if (LOGD) {
3544 Log.d(TAG, "Deferring update until onResume");
3545 }
3546 if (deletePreviousRunnables) {
3547 while (mBindOnResumeCallbacks.remove(run)) {
3548 }
3549 }
3550 mBindOnResumeCallbacks.add(run);
3551 return true;
3552 } else {
3553 return false;
3554 }
3555 }
3556
3557 private boolean waitUntilResume(Runnable run) {
3558 return waitUntilResume(run, false);
3559 }
3560
3561 public void addOnResumeCallback(Runnable run) {
3562 mOnResumeCallbacks.add(run);
3563 }
3564
3565 /**
3566 * If the activity is currently paused, signal that we need to re-run the loader
3567 * in onResume.
3568 *
3569 * This needs to be called from incoming places where resources might have been loaded
3570 * while we are paused. That is becaues the Configuration might be wrong
3571 * when we're not running, and if it comes back to what it was when we
3572 * were paused, we are not restarted.
3573 *
3574 * Implementation of the method from LauncherModel.Callbacks.
3575 *
3576 * @return true if we are currently paused. The caller might be able to
3577 * skip some work in that case since we will come back again.
3578 */
3579 public boolean setLoadOnResume() {
3580 if (mPaused) {
3581 if (LOGD) {
3582 Log.d(TAG, "setLoadOnResume");
3583 }
3584 mOnResumeNeedsLoad = true;
3585 return true;
3586 } else {
3587 return false;
3588 }
3589 }
3590
3591 /**
3592 * Implementation of the method from LauncherModel.Callbacks.
3593 */
3594 public int getCurrentWorkspaceScreen() {
3595 if (mWorkspace != null) {
3596 return mWorkspace.getCurrentPage();
3597 } else {
3598 return SCREEN_COUNT / 2;
3599 }
3600 }
3601
3602 /**
3603 * Refreshes the shortcuts shown on the workspace.
3604 *
3605 * Implementation of the method from LauncherModel.Callbacks.
3606 */
3607 public void startBinding() {
3608 setWorkspaceLoading(true);
3609
3610 // If we're starting binding all over again, clear any bind calls we'd postponed in
3611 // the past (see waitUntilResume) -- we don't need them since we're starting binding
3612 // from scratch again
3613 mBindOnResumeCallbacks.clear();
3614
3615 // Clear the workspace because it's going to be rebound
3616 mWorkspace.clearDropTargets();
3617 mWorkspace.removeAllWorkspaceScreens();
3618
3619 mWidgetsToAdvance.clear();
3620 if (mHotseat != null) {
3621 mHotseat.resetLayout();
3622 }
3623 }
3624
3625 @Override
3626 public void bindScreens(ArrayList<Long> orderedScreenIds) {
3627 bindAddScreens(orderedScreenIds);
3628
3629 // If there are no screens, we need to have an empty screen
3630 if (orderedScreenIds.size() == 0) {
3631 mWorkspace.addExtraEmptyScreen();
3632 }
3633
3634 // Create the custom content page (this call updates mDefaultScreen which calls
3635 // setCurrentPage() so ensure that all pages are added before calling this).
3636 if (hasCustomContentToLeft()) {
3637 mWorkspace.createCustomContentContainer();
3638 populateCustomContentContainer();
3639 }
3640 }
3641
3642 @Override
3643 public void bindAddScreens(ArrayList<Long> orderedScreenIds) {
3644 // Log to disk
3645 Launcher.addDumpLog(TAG, "11683562 - bindAddScreens()", true);
3646 Launcher.addDumpLog(TAG, "11683562 - orderedScreenIds: " +
3647 TextUtils.join(", ", orderedScreenIds), true);
3648 int count = orderedScreenIds.size();
3649 for (int i = 0; i < count; i++) {
3650 mWorkspace.insertNewWorkspaceScreenBeforeEmptyScreen(orderedScreenIds.get(i));
3651 }
3652 }
3653
3654 private boolean shouldShowWeightWatcher() {
3655 String spKey = LauncherAppState.getSharedPreferencesKey();
3656 SharedPreferences sp = getSharedPreferences(spKey, Context.MODE_PRIVATE);
3657 boolean show = sp.getBoolean(SHOW_WEIGHT_WATCHER, SHOW_WEIGHT_WATCHER_DEFAULT);
3658
3659 return show;
3660 }
3661
3662 private void toggleShowWeightWatcher() {
3663 String spKey = LauncherAppState.getSharedPreferencesKey();
3664 SharedPreferences sp = getSharedPreferences(spKey, Context.MODE_PRIVATE);
3665 boolean show = sp.getBoolean(SHOW_WEIGHT_WATCHER, true);
3666
3667 show = !show;
3668
3669 SharedPreferences.Editor editor = sp.edit();
3670 editor.putBoolean(SHOW_WEIGHT_WATCHER, show);
3671 editor.commit();
3672
3673 if (mWeightWatcher != null) {
3674 mWeightWatcher.setVisibility(show ? View.VISIBLE : View.GONE);
3675 }
3676 }
3677
3678 public void bindAppsAdded(final ArrayList<Long> newScreens, final ArrayList<ItemInfo> addNotAnimated,🔵
3679 Runnable r = new Runnable() {
3680 public void run() {
3681 bindAppsAdded(newScreens, addNotAnimated, addAnimated, addedApps);
3682 }
3683 };
3684 if (waitUntilResume(r)) {
3685 return;
3686 }
3687 // Add the new screens
3688 if (newScreens != null) {
3689 bindAddScreens(newScreens);
3690 }
3691 // We add the items without animation on non-visible pages, and with
3692 // animations on the new page (which we will try and snap to).
3693 if ((addNotAnimated != null) && (!addNotAnimated.isEmpty())) {
3694 bindItems(addNotAnimated, 0, addNotAnimated.size(), false);
3695 }
3696 if ((addAnimated != null) && (!addAnimated.isEmpty())) {
3697 bindItems(addAnimated, 0, addAnimated.size(), true);
3698 }
3699 // Remove the extra empty screen
3700 mWorkspace.removeExtraEmptyScreen(false, false);
3701 if ((addedApps != null) && (mAppsView != null)) {
3702 mAppsView.addApps(addedApps);
3703 }
3704 }
3705
3706 /**
3707 * Bind the items start-end from the list.
3708 *
3709 * Implementation of the method from LauncherModel.Callbacks.
3710 */
3711 public void bindItems(final ArrayList<ItemInfo> shortcuts, final int start, final int end,
3712 final boolean forceAnimateIcons) {
3713 Runnable r = new Runnable() {
3714 public void run() {
3715 bindItems(shortcuts, start, end, forceAnimateIcons);
3716 }
3717 };
3718 if (waitUntilResume(r)) {
3719 return;
3720 }
3721
3722 // Get the list of added shortcuts and intersect them with the set of shortcuts here
3723 final AnimatorSet anim = LauncherAnimUtils.createAnimatorSet();
3724 final Collection<Animator> bounceAnims = new ArrayList<Animator>();
3725 final boolean animateIcons = forceAnimateIcons && canRunNewAppsAnimation();
3726 Workspace workspace = mWorkspace;
3727 long newShortcutsScreenId = -1;
3728 for (int i = start; i < end; i++) {
3729 final ItemInfo item = shortcuts.get(i);
3730
3731 // Short circuit if we are loading dock items for a configuration which has no dock
3732 if (item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT &&
3733 mHotseat == null) {
3734 continue;
3735 }
3736
3737 switch (item.itemType) {
3738 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
3739 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
3740 ShortcutInfo info = (ShortcutInfo) item;
3741 View shortcut = createShortcut(info);
3742
3743 /*
3744 * TODO: FIX collision case
3745 */
3746 if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
3747 CellLayout cl = mWorkspace.getScreenWithId(item.screenId);
3748 if (cl != null && cl.isOccupied(item.cellX, item.cellY)) {
3749 View v = cl.getChildAt(item.cellX, item.cellY);
3750 Object tag = v.getTag();
3751 String desc = "Collision while binding workspace item: " + item
3752 + ". Collides with " + tag;
3753 if (LauncherAppState.isDogfoodBuild()) {
3754 throw (new RuntimeException(desc));
3755 } else {
3756 Log.d(TAG, desc);
3757 }
3758 }
3759 }
3760
3761 workspace.addInScreenFromBind(shortcut, item.container, item.screenId, item.cellX,
3762 item.cellY, 1, 1);
3763 if (animateIcons) {
3764 // Animate all the applications up now
3765 shortcut.setAlpha(0f);
3766 shortcut.setScaleX(0f);
3767 shortcut.setScaleY(0f);
3768 bounceAnims.add(createNewAppBounceAnimation(shortcut, i));
3769 newShortcutsScreenId = item.screenId;
3770 }
3771 break;
3772 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
3773 FolderIcon newFolder = FolderIcon.fromXml(R.layout.folder_icon, this,
3774 (ViewGroup) workspace.getChildAt(workspace.getCurrentPage()),
3775 (FolderInfo) item, mIconCache);
3776 workspace.addInScreenFromBind(newFolder, item.container, item.screenId, item.cellX,
3777 item.cellY, 1, 1);
3778 break;
3779 default:
3780 throw new RuntimeException("Invalid Item Type");
3781 }
3782 }
3783
3784 if (animateIcons) {
3785 // Animate to the correct page
3786 if (newShortcutsScreenId > -1) {
3787 long currentScreenId = mWorkspace.getScreenIdForPageIndex(mWorkspace.getNextPage());
3788 final int newScreenIndex = mWorkspace.getPageIndexForScreenId(newShortcutsScreenId);
3789 final Runnable startBounceAnimRunnable = new Runnable() {
3790 public void run() {
3791 anim.playTogether(bounceAnims);
3792 anim.start();
3793 }
3794 };
3795 if (newShortcutsScreenId != currentScreenId) {
3796 // We post the animation slightly delayed to prevent slowdowns
3797 // when we are loading right after we return to launcher.
3798 mWorkspace.postDelayed(new Runnable() {
3799 public void run() {
3800 if (mWorkspace != null) {
3801 mWorkspace.snapToPage(newScreenIndex);
3802 mWorkspace.postDelayed(startBounceAnimRunnable,
3803 NEW_APPS_ANIMATION_DELAY);
3804 }
3805 }
3806 }, NEW_APPS_PAGE_MOVE_DELAY);
3807 } else {
3808 mWorkspace.postDelayed(startBounceAnimRunnable, NEW_APPS_ANIMATION_DELAY);
3809 }
3810 }
3811 }
3812 workspace.requestLayout();
3813 }
3814
3815 /**
3816 * Implementation of the method from LauncherModel.Callbacks.
3817 */
3818 public void bindFolders(final LongArrayMap<FolderInfo> folders) {
3819 Runnable r = new Runnable() {
3820 public void run() {
3821 bindFolders(folders);
3822 }
3823 };
3824 if (waitUntilResume(r)) {
3825 return;
3826 }
3827 sFolders = folders.clone();
3828 }
3829
3830 /**
3831 * Add the views for a widget to the workspace.
3832 *
3833 * Implementation of the method from LauncherModel.Callbacks.
3834 */
3835 public void bindAppWidget(final LauncherAppWidgetInfo item) {
3836 Runnable r = new Runnable() {
3837 public void run() {
3838 bindAppWidget(item);
3839 }
3840 };
3841 if (waitUntilResume(r)) {
3842 return;
3843 }
3844 final long start = (DEBUG_WIDGETS) ? SystemClock.uptimeMillis() : 0;
3845 if (DEBUG_WIDGETS) {
3846 Log.d(TAG, "bindAppWidget: " + item);
3847 }
3848 final Workspace workspace = mWorkspace;
3849 LauncherAppWidgetProviderInfo appWidgetInfo = LauncherModel.getProviderInfo(this, item.providerNa🔵
3850 if (((!mIsSafeModeEnabled) && ((item.restoreStatus & LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READ🔵
3851 if (appWidgetInfo == null) {
3852 if (DEBUG_WIDGETS) {
3853 Log.d(TAG, ((("Removing restored widget: id=" + item.appWidgetId) + " belongs to comp🔵
3854 }
3855 LauncherModel.deleteItemFromDatabase(this, item);
3856 return;
3857 }
3858 // Note: This assumes that the id remap broadcast is received before this step.
3859 // If that is not the case, the id remap will be ignored and user may see the
3860 // click to setup view.
3861 PendingAddWidgetInfo pendingInfo = new PendingAddWidgetInfo(this, appWidgetInfo, null);
3862 pendingInfo.spanX = item.spanX;
3863 pendingInfo.spanY = item.spanY;
3864 pendingInfo.minSpanX = item.minSpanX;
3865 pendingInfo.minSpanY = item.minSpanY;
3866 Bundle options = null;
3867 WidgetHostViewLoader.getDefaultOptionsForWidget(this, pendingInfo);
3868 int newWidgetId = mAppWidgetHost.allocateAppWidgetId();
3869 boolean success = mAppWidgetManager.bindAppWidgetIdIfAllowed(newWidgetId, appWidgetInfo, opti🔵
3870 // TODO consider showing a permission dialog when the widget is clicked.
3871 if (!success) {
3872 mAppWidgetHost.deleteAppWidgetId(newWidgetId);
3873 if (DEBUG_WIDGETS) {
3874 Log.d(TAG, ((("Removing restored widget: id=" + item.appWidgetId) + " belongs to comp🔵
3875 }
3876 LauncherModel.deleteItemFromDatabase(this, item);
3877 return;
3878 }
3879 item.appWidgetId = newWidgetId;
3880 // If the widget has a configure activity, it is still needs to set it up, otherwise
3881 // the widget is ready to go.
3882 item.restoreStatus = (appWidgetInfo.configure == null) ? LauncherAppWidgetInfo.RESTORE_COMPLE🔵
3883 LauncherModel.updateItemInDatabase(this, item);
3884 }
3885 if ((!mIsSafeModeEnabled) && (item.restoreStatus == LauncherAppWidgetInfo.RESTORE_COMPLETED)) {
3886 final int appWidgetId = item.appWidgetId;
3887 if (DEBUG_WIDGETS) {
3888 Log.d(TAG, (("bindAppWidget: id=" + item.appWidgetId) + " belongs to component ") + appWi🔵
3889 }
3890 item.hostView = mAppWidgetHost.createView(this, appWidgetId, appWidgetInfo);
3891 } else {
3892 appWidgetInfo = null;
3893 PendingAppWidgetHostView view = new PendingAppWidgetHostView(this, item, mIsSafeModeEnabled);
3894 view.updateIcon(mIconCache);
3895 item.hostView = view;
3896 item.hostView.updateAppWidget(null);
3897 item.hostView.setOnClickListener(this);
3898 }
3899 item.hostView.setTag(item);
3900 item.onBindAppWidget(this);
3901 workspace.addInScreen(item.hostView, item.container, item.screenId, item.cellX, item.cellY, item.🔵
3902 if (!item.isCustomWidget()) {
3903 addWidgetToAutoAdvanceIfNeeded(item.hostView, appWidgetInfo);
3904 }
3905 workspace.requestLayout();
3906 if (DEBUG_WIDGETS) {
3907 Log.d(TAG, ((("bound widget id=" + item.appWidgetId) + " in ") + (SystemClock.uptimeMillis() 🔵
3908 }
3909 }
3910
3911 /**
3912 * Restores a pending widget.
3913 *
3914 * @param appWidgetId The app widget id
3915 * @param cellInfo The position on screen where to create the widget.
3916 */
3917 private void completeRestoreAppWidget(final int appWidgetId) {
3918 LauncherAppWidgetHostView view = mWorkspace.getWidgetForAppWidgetId(appWidgetId);
3919 if ((view == null) || !(view instanceof PendingAppWidgetHostView)) {
3920 Log.e(TAG, "Widget update called, when the widget no longer exists.");
3921 return;
3922 }
3923
3924 LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) view.getTag();
3925 info.restoreStatus = LauncherAppWidgetInfo.RESTORE_COMPLETED;
3926
3927 mWorkspace.reinflateWidgetsIfNecessary();
3928 LauncherModel.updateItemInDatabase(this, info);
3929 }
3930
3931 public void onPageBoundSynchronously(int page) {
3932 mSynchronouslyBoundPages.add(page);
3933 }
3934
3935 /**
3936 * Callback saying that there aren't any more items to bind.
3937 *
3938 * Implementation of the method from LauncherModel.Callbacks.
3939 */
3940 public void finishBindingItems() {
3941 Runnable r = new Runnable() {
3942 public void run() {
3943 finishBindingItems();
3944 }
3945 };
3946 if (waitUntilResume(r)) {
3947 return;
3948 }
3949 if (mSavedState != null) {
3950 if (!mWorkspace.hasFocus()) {
3951 mWorkspace.getChildAt(mWorkspace.getCurrentPage()).requestFocus();
3952 }
3953 mSavedState = null;
3954 }
3955 mWorkspace.restoreInstanceStateForRemainingPages();
3956 setWorkspaceLoading(false);
3957 sendLoadingCompleteBroadcastIfNecessary();
3958 // If we received the result of any pending adds while the loader was running (e.g. the
3959 // widget configuration forced an orientation change), process them now.
3960 if (sPendingAddItem != null) {
3961 final long screenId = completeAdd(sPendingAddItem);
3962 // TODO: this moves the user to the page where the pending item was added. Ideally,
3963 // the screen would be guaranteed to exist after bind, and the page would be set through
3964 // the workspace restore process.
3965 mWorkspace.post(new Runnable() {
3966 @Override
3967 public void run() {
3968 mWorkspace.snapToScreenId(screenId);
3969 }
3970 });
3971 sPendingAddItem = null;
3972 }
3973 InstallShortcutReceiver.disableAndFlushInstallQueue(this);
3974 if (mLauncherCallbacks != null) {
3975 mLauncherCallbacks.finishBindingItems(false);
3976 }
3977 }
3978
3979 private void sendLoadingCompleteBroadcastIfNecessary() {
3980 if (!mSharedPrefs.getBoolean(FIRST_LOAD_COMPLETE, false)) {
3981 String permission =
3982 getResources().getString(R.string.receive_first_load_broadcast_permission);
3983 Intent intent = new Intent(ACTION_FIRST_LOAD_COMPLETE);
3984 sendBroadcast(intent, permission);
3985 SharedPreferences.Editor editor = mSharedPrefs.edit();
3986 editor.putBoolean(FIRST_LOAD_COMPLETE, true);
3987 editor.apply();
3988 }
3989 }
3990
3991 public boolean isAllAppsButtonRank(int rank) {
3992 if (mHotseat != null) {
3993 return mHotseat.isAllAppsButtonRank(rank);
3994 }
3995 return false;
3996 }
3997
3998 private boolean canRunNewAppsAnimation() {
3999 long diff = System.currentTimeMillis() - mDragController.getLastGestureUpTime();
4000 return diff > (NEW_APPS_ANIMATION_INACTIVE_TIMEOUT_SECONDS * 1000);
4001 }
4002
4003 private ValueAnimator createNewAppBounceAnimation(View v, int i) {
4004 ValueAnimator bounceAnim = LauncherAnimUtils.ofPropertyValuesHolder(v,
4005 PropertyValuesHolder.ofFloat("alpha", 1f),
4006 PropertyValuesHolder.ofFloat("scaleX", 1f),
4007 PropertyValuesHolder.ofFloat("scaleY", 1f));
4008 bounceAnim.setDuration(InstallShortcutReceiver.NEW_SHORTCUT_BOUNCE_DURATION);
4009 bounceAnim.setStartDelay(i * InstallShortcutReceiver.NEW_SHORTCUT_STAGGER_DELAY);
4010 bounceAnim.setInterpolator(new SmoothPagedView.OvershootInterpolator());
4011 return bounceAnim;
4012 }
4013
4014 public boolean useVerticalBarLayout() {
4015 return mDeviceProfile.isVerticalBarLayout();
4016 }
4017
4018 protected Rect getSearchBarBounds() {
4019 return mDeviceProfile.getSearchBarBounds(Utilities.isRtl(getResources()));
4020 }
4021
4022 public void bindSearchablesChanged() {
4023 if (mSearchDropTargetBar == null) {
4024 return;
4025 }
4026 if (mQsb != null) {
4027 mSearchDropTargetBar.removeView(mQsb);
4028 mQsb = null;
4029 }
4030 mSearchDropTargetBar.setQsbSearchBar(getOrCreateQsbBar());
4031 }
4032
4033 /**
4034 * A runnable that we can dequeue and re-enqueue when all applications are bound (to prevent
4035 * multiple calls to bind the same list.)
4036 */
4037 @Thunk ArrayList<AppInfo> mTmpAppsList;
4038
4039 private Runnable mBindAllApplicationsRunnable = new Runnable() {
4040 public void run() {
4041 bindAllApplications(mTmpAppsList);
4042 mTmpAppsList = null;
4043 }
4044 };
4045
4046 /**
4047 * Add the icons for all apps.
4048 *
4049 * Implementation of the method from LauncherModel.Callbacks.
4050 */
4051 public void bindAllApplications(final ArrayList<AppInfo> apps) {
4052 if (waitUntilResume(mBindAllApplicationsRunnable, true)) {
4053 mTmpAppsList = apps;
4054 return;
4055 }
4056 if (mAppsView != null) {
4057 mAppsView.setApps(apps);
4058 }
4059 if (mLauncherCallbacks != null) {
4060 mLauncherCallbacks.bindAllApplications(apps);
4061 }
4062 }
4063
4064 /**
4065 * A package was updated.
4066 *
4067 * Implementation of the method from LauncherModel.Callbacks.
4068 */
4069 public void bindAppsUpdated(final ArrayList<AppInfo> apps) {
4070 Runnable r = new Runnable() {
4071 public void run() {
4072 bindAppsUpdated(apps);
4073 }
4074 };
4075 if (waitUntilResume(r)) {
4076 return;
4077 }
4078 if (mAppsView != null) {
4079 mAppsView.updateApps(apps);
4080 }
4081 }
4082
4083 @Override
4084 public void bindWidgetsRestored(final ArrayList<LauncherAppWidgetInfo> widgets) {
4085 Runnable r = new Runnable() {
4086 public void run() {
4087 bindWidgetsRestored(widgets);
4088 }
4089 };
4090 if (waitUntilResume(r)) {
4091 return;
4092 }
4093 mWorkspace.widgetsRestored(widgets);
4094 }
4095
4096 /**
4097 * Some shortcuts were updated in the background.
4098 *
4099 * Implementation of the method from LauncherModel.Callbacks.
4100 */
4101 @Override
4102 public void bindShortcutsChanged(final ArrayList<ShortcutInfo> updated,
4103 final ArrayList<ShortcutInfo> removed, final UserHandleCompat user) {
4104 Runnable r = new Runnable() {
4105 public void run() {
4106 bindShortcutsChanged(updated, removed, user);
4107 }
4108 };
4109 if (waitUntilResume(r)) {
4110 return;
4111 }
4112
4113 if (!updated.isEmpty()) {
4114 mWorkspace.updateShortcuts(updated);
4115 }
4116
4117 if (!removed.isEmpty()) {
4118 HashSet<ComponentName> removedComponents = new HashSet<ComponentName>();
4119 for (ShortcutInfo si : removed) {
4120 removedComponents.add(si.getTargetComponent());
4121 }
4122 mWorkspace.removeItemsByComponentName(removedComponents, user);
4123 // Notify the drag controller
4124 mDragController.onAppsRemoved(new ArrayList<String>(), removedComponents);
4125 }
4126 }
4127
4128 /**
4129 * Update the state of a package, typically related to install state.
4130 *
4131 * Implementation of the method from LauncherModel.Callbacks.
4132 */
4133 @Override
4134 public void bindRestoreItemsChange(final HashSet<ItemInfo> updates) {
4135 Runnable r = new Runnable() {
4136 public void run() {
4137 bindRestoreItemsChange(updates);
4138 }
4139 };
4140 if (waitUntilResume(r)) {
4141 return;
4142 }
4143 mWorkspace.updateRestoreItems(updates);
4144 }
4145
4146 /**
4147 * A package was uninstalled. We take both the super set of packageNames
4148 * in addition to specific applications to remove, the reason being that
4149 * this can be called when a package is updated as well. In that scenario,
4150 * we only remove specific components from the workspace, where as
4151 * package-removal should clear all items by package name.
4152 *
4153 * @param reason if non-zero, the icons are not permanently removed, rather marked as disabled.
4154 * Implementation of the method from LauncherModel.Callbacks.
4155 */
4156 @Override
4157 public void bindComponentsRemoved(final ArrayList<String> packageNames, final ArrayList<AppInfo> appI🔵
4158 Runnable r = new Runnable() {
4159 public void run() {
4160 bindComponentsRemoved(packageNames, appInfos, user, reason);
4161 }
4162 };
4163 if (waitUntilResume(r)) {
4164 return;
4165 }
4166 if (reason == 0) {
4167 HashSet<ComponentName> removedComponents = new HashSet<ComponentName>();
4168 for (AppInfo info : appInfos) {
4169 removedComponents.add(info.componentName);
4170 }
4171 if (!packageNames.isEmpty()) {
4172 mWorkspace.removeItemsByPackageName(packageNames, user);
4173 }
4174 if (!removedComponents.isEmpty()) {
4175 mWorkspace.removeItemsByComponentName(removedComponents, user);
4176 }
4177 // Notify the drag controller
4178 mDragController.onAppsRemoved(packageNames, removedComponents);
4179 } else {
4180 mWorkspace.disableShortcutsByPackageName(packageNames, user, reason);
4181 }
4182 // Update AllApps
4183 if (mAppsView != null) {
4184 mAppsView.removeApps(appInfos);
4185 }
4186 }
4187
4188 private Runnable mBindPackagesUpdatedRunnable = new Runnable() {
4189 public void run() {
4190 bindAllPackages(mWidgetsModel);
4191 }
4192 };
4193
4194 @Override
4195 public void bindAllPackages(final WidgetsModel model) {
4196 if (waitUntilResume(mBindPackagesUpdatedRunnable, true)) {
4197 mWidgetsModel = model;
4198 return;
4199 }
4200 if ((mWidgetsView != null) && (model != null)) {
4201 mWidgetsView.addWidgets(model);
4202 mWidgetsModel = null;
4203 }
4204 }
4205
4206 private int mapConfigurationOriActivityInfoOri(int configOri) {
4207 final Display d = getWindowManager().getDefaultDisplay();
4208 int naturalOri = Configuration.ORIENTATION_LANDSCAPE;
4209 switch (d.getRotation()) {
4210 case Surface.ROTATION_0:
4211 case Surface.ROTATION_180:
4212 // We are currently in the same basic orientation as the natural orientation
4213 naturalOri = configOri;
4214 break;
4215 case Surface.ROTATION_90:
4216 case Surface.ROTATION_270:
4217 // We are currently in the other basic orientation to the natural orientation
4218 naturalOri = (configOri == Configuration.ORIENTATION_LANDSCAPE) ?
4219 Configuration.ORIENTATION_PORTRAIT : Configuration.ORIENTATION_LANDSCAPE;
4220 break;
4221 }
4222
4223 int[] oriMap = {
4224 ActivityInfo.SCREEN_ORIENTATION_PORTRAIT,
4225 ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE,
4226 ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT,
4227 ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE
4228 };
4229 // Since the map starts at portrait, we need to offset if this device's natural orientation
4230 // is landscape.
4231 int indexOffset = 0;
4232 if (naturalOri == Configuration.ORIENTATION_LANDSCAPE) {
4233 indexOffset = 1;
4234 }
4235 return oriMap[(d.getRotation() + indexOffset) % 4];
4236 }
4237
4238 public void lockScreenOrientation() {
4239 if (Utilities.isRotationEnabled(this)) {
4240 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
4241 setRequestedOrientation(mapConfigurationOriActivityInfoOri(getResources()
4242 .getConfiguration().orientation));
4243 } else {
4244 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LOCKED);
4245 }
4246 }
4247 }
4248
4249 public void unlockScreenOrientation(boolean immediate) {
4250 if (Utilities.isRotationEnabled(this)) {
4251 if (immediate) {
4252 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
4253 } else {
4254 mHandler.postDelayed(new Runnable() {
4255 public void run() {
4256 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
4257 }
4258 }, mRestoreScreenOrientationDelay);
4259 }
4260 }
4261 }
4262
4263 protected boolean isLauncherPreinstalled() {
4264 if (mLauncherCallbacks != null) {
4265 return mLauncherCallbacks.isLauncherPreinstalled();
4266 }
4267 PackageManager pm = getPackageManager();
4268 try {
4269 ApplicationInfo ai = pm.getApplicationInfo(getComponentName().getPackageName(), 0);
4270 if ((ai.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
4271 return true;
4272 } else {
4273 return false;
4274 }
4275 } catch (NameNotFoundException e) {
4276 e.printStackTrace();
4277 return false;
4278 }
4279 }
4280
4281 /**
4282 * This method indicates whether or not we should suggest default wallpaper dimensions
4283 * when our wallpaper cropper was not yet used to set a wallpaper.
4284 */
4285 protected boolean overrideWallpaperDimensions() {
4286 if (mLauncherCallbacks != null) {
4287 return mLauncherCallbacks.overrideWallpaperDimensions();
4288 }
4289 return true;
4290 }
4291
4292 /**
4293 * To be overridden by subclasses to indicate that there is an activity to launch
4294 * before showing the standard launcher experience.
4295 */
4296 protected boolean hasFirstRunActivity() {
4297 if (mLauncherCallbacks != null) {
4298 return mLauncherCallbacks.hasFirstRunActivity();
4299 }
4300 return false;
4301 }
4302
4303 /**
4304 * To be overridden by subclasses to launch any first run activity
4305 */
4306 protected Intent getFirstRunActivity() {
4307 if (mLauncherCallbacks != null) {
4308 return mLauncherCallbacks.getFirstRunActivity();
4309 }
4310 return null;
4311 }
4312
4313 /**
4314 * Returns whether the launcher callbacks overrides search in all apps.
4315 */
4316 @Thunk
4317 boolean isAllAppsSearchOverridden() {
4318 if (DISABLE_ALL_APPS_SEARCH_INTEGRATION) {
4319 return false;
4320 }
4321 if (mLauncherCallbacks != null) {
4322 return mLauncherCallbacks.overrideAllAppsSearch();
4323 }
4324 return false;
4325 }
4326
4327 private boolean shouldRunFirstRunActivity() {
4328 return !ActivityManager.isRunningInTestHarness() &&
4329 !mSharedPrefs.getBoolean(FIRST_RUN_ACTIVITY_DISPLAYED, false);
4330 }
4331
4332 protected boolean hasRunFirstRunActivity() {
4333 return mSharedPrefs.getBoolean(FIRST_RUN_ACTIVITY_DISPLAYED, false);
4334 }
4335
4336 public boolean showFirstRunActivity() {
4337 if (shouldRunFirstRunActivity() &&
4338 hasFirstRunActivity()) {
4339 Intent firstRunIntent = getFirstRunActivity();
4340 if (firstRunIntent != null) {
4341 startActivity(firstRunIntent);
4342 markFirstRunActivityShown();
4343 return true;
4344 }
4345 }
4346 return false;
4347 }
4348
4349 private void markFirstRunActivityShown() {
4350 SharedPreferences.Editor editor = mSharedPrefs.edit();
4351 editor.putBoolean(FIRST_RUN_ACTIVITY_DISPLAYED, true);
4352 editor.apply();
4353 }
4354
4355 /**
4356 * To be overridden by subclasses to indicate that there is an in-activity full-screen intro
4357 * screen that must be displayed and dismissed.
4358 */
4359 protected boolean hasDismissableIntroScreen() {
4360 if (mLauncherCallbacks != null) {
4361 return mLauncherCallbacks.hasDismissableIntroScreen();
4362 }
4363 return false;
4364 }
4365
4366 /**
4367 * Full screen intro screen to be shown and dismissed before the launcher can be used.
4368 */
4369 protected View getIntroScreen() {
4370 if (mLauncherCallbacks != null) {
4371 return mLauncherCallbacks.getIntroScreen();
4372 }
4373 return null;
4374 }
4375
4376 /**
4377 * To be overriden by subclasses to indicate whether the in-activity intro screen has been
4378 * dismissed. This method is ignored if #hasDismissableIntroScreen returns false.
4379 */
4380 private boolean shouldShowIntroScreen() {
4381 return hasDismissableIntroScreen() &&
4382 !mSharedPrefs.getBoolean(INTRO_SCREEN_DISMISSED, false);
4383 }
4384
4385 protected void showIntroScreen() {
4386 View introScreen = getIntroScreen();
4387 changeWallpaperVisiblity(false);
4388 if (introScreen != null) {
4389 mDragLayer.showOverlayView(introScreen);
4390 }
4391 if (mLauncherOverlayContainer != null) {
4392 mLauncherOverlayContainer.setVisibility(View.INVISIBLE);
4393 }
4394 }
4395
4396 public void dismissIntroScreen() {
4397 markIntroScreenDismissed();
4398 if (showFirstRunActivity()) {
4399 // We delay hiding the intro view until the first run activity is showing. This
4400 // avoids a blip.
4401 mWorkspace.postDelayed(new Runnable() {
4402 @Override
4403 public void run() {
4404 mDragLayer.dismissOverlayView();
4405 if (mLauncherOverlayContainer != null) {
4406 mLauncherOverlayContainer.setVisibility(View.VISIBLE);
4407 }
4408 showFirstRunClings();
4409 }
4410 }, ACTIVITY_START_DELAY);
4411 } else {
4412 mDragLayer.dismissOverlayView();
4413 if (mLauncherOverlayContainer != null) {
4414 mLauncherOverlayContainer.setVisibility(View.VISIBLE);
4415 }
4416 showFirstRunClings();
4417 }
4418 changeWallpaperVisiblity(true);
4419 }
4420
4421 private void markIntroScreenDismissed() {
4422 SharedPreferences.Editor editor = mSharedPrefs.edit();
4423 editor.putBoolean(INTRO_SCREEN_DISMISSED, true);
4424 editor.apply();
4425 }
4426
4427 @Thunk
4428 void showFirstRunClings() {
4429 // The two first run cling paths are mutually exclusive, if the launcher is preinstalled
4430 // on the device, then we always show the first run cling experience (or if there is no
4431 // launcher2). Otherwise, we prompt the user upon started for migration
4432 LauncherClings launcherClings = new LauncherClings(this);
4433 if (launcherClings.shouldShowFirstRunOrMigrationClings()) {
4434 if (mModel.canMigrateFromOldLauncherDb(this)) {
4435 launcherClings.showMigrationCling();
4436 } else {
4437 launcherClings.showLongPressCling(true);
4438 }
4439 }
4440 }
4441
4442 void showWorkspaceSearchAndHotseat() {
4443 if (mWorkspace != null) mWorkspace.setAlpha(1f);
4444 if (mHotseat != null) mHotseat.setAlpha(1f);
4445 if (mPageIndicators != null) mPageIndicators.setAlpha(1f);
4446 if (mSearchDropTargetBar != null) mSearchDropTargetBar.showSearchBar(false);
4447 }
4448
4449 void hideWorkspaceSearchAndHotseat() {
4450 if (mWorkspace != null) mWorkspace.setAlpha(0f);
4451 if (mHotseat != null) mHotseat.setAlpha(0f);
4452 if (mPageIndicators != null) mPageIndicators.setAlpha(0f);
4453 if (mSearchDropTargetBar != null) mSearchDropTargetBar.hideSearchBar(false);
4454 }
4455
4456 public ItemInfo createAppDragInfo(Intent appLaunchIntent) {
4457 // Called from search suggestion, not supported in other profiles.
4458 final UserHandleCompat myUser = UserHandleCompat.myUserHandle();
4459 LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(this);
4460 LauncherActivityInfoCompat activityInfo = launcherApps.resolveActivity(appLaunchIntent, myUser);
4461 if (activityInfo == null) {
4462 return null;
4463 }
4464 return new AppInfo(this, activityInfo, myUser, mIconCache);
4465 }
4466
4467 public ItemInfo createShortcutDragInfo(Intent shortcutIntent, CharSequence caption,
4468 Bitmap icon) {
4469 // Called from search suggestion, not supported in other profiles.
4470 return createShortcutDragInfo(shortcutIntent, caption, icon,
4471 UserHandleCompat.myUserHandle());
4472 }
4473
4474 public ItemInfo createShortcutDragInfo(Intent shortcutIntent, CharSequence caption,
4475 Bitmap icon, UserHandleCompat user) {
4476 UserManagerCompat userManager = UserManagerCompat.getInstance(this);
4477 CharSequence contentDescription = userManager.getBadgedLabelForUser(caption, user);
4478 return new ShortcutInfo(shortcutIntent, caption, contentDescription, icon, user);
4479 }
4480
4481 protected void moveWorkspaceToDefaultScreen() {
4482 mWorkspace.moveToDefaultScreen(false);
4483 }
4484
4485 public void startDrag(View dragView, ItemInfo dragInfo, DragSource source) {
4486 dragView.setTag(dragInfo);
4487 mWorkspace.onExternalDragStartedWithItem(dragView);
4488 mWorkspace.beginExternalDragShared(dragView, source);
4489 }
4490
4491 @Override
4492 public void onPageSwitch(View newPage, int newPageIndex) {
4493 if (mLauncherCallbacks != null) {
4494 mLauncherCallbacks.onPageSwitch(newPage, newPageIndex);
4495 }
4496 }
4497
4498 /**
4499 * Prints out out state for debugging.
4500 */
4501 public void dumpState() {
4502 Log.d(TAG, "BEGIN launcher3 dump state for launcher " + this);
4503 Log.d(TAG, "mSavedState=" + mSavedState);
4504 Log.d(TAG, "mWorkspaceLoading=" + mWorkspaceLoading);
4505 Log.d(TAG, "mRestoring=" + mRestoring);
4506 Log.d(TAG, "mWaitingForResult=" + mWaitingForResult);
4507 Log.d(TAG, "mSavedInstanceState=" + mSavedInstanceState);
4508 Log.d(TAG, "sFolders.size=" + sFolders.size());
4509 mModel.dumpState();
4510 // TODO(hyunyoungs): add mWidgetsView.dumpState(); or mWidgetsModel.dumpState();
4511 Log.d(TAG, "END launcher3 dump state");
4512 }
4513
4514 @Override
4515 public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
4516 super.dump(prefix, fd, writer, args);
4517 synchronized (sDumpLogs) {
4518 writer.println(" ");
4519 writer.println("Debug logs: ");
4520 for (int i = 0; i < sDumpLogs.size(); i++) {
4521 writer.println(" " + sDumpLogs.get(i));
4522 }
4523 }
4524 if (mLauncherCallbacks != null) {
4525 mLauncherCallbacks.dump(prefix, fd, writer, args);
4526 }
4527 }
4528
4529 public static void dumpDebugLogsToConsole() {
4530 if (DEBUG_DUMP_LOG) {
4531 synchronized (sDumpLogs) {
4532 Log.d(TAG, "");
4533 Log.d(TAG, "*********************");
4534 Log.d(TAG, "Launcher debug logs: ");
4535 for (int i = 0; i < sDumpLogs.size(); i++) {
4536 Log.d(TAG, " " + sDumpLogs.get(i));
4537 }
4538 Log.d(TAG, "*********************");
4539 Log.d(TAG, "");
4540 }
4541 }
4542 }
4543
4544 public static void addDumpLog(String tag, String log, boolean debugLog) {
4545 addDumpLog(tag, log, null, debugLog);
4546 }
4547
4548 public static void addDumpLog(String tag, String log, Exception e, boolean debugLog) {
4549 if (debugLog) {
4550 if (e != null) {
4551 Log.d(tag, log, e);
4552 } else {
4553 Log.d(tag, log);
4554 }
4555 }
4556 if (DEBUG_DUMP_LOG) {
4557 sDateStamp.setTime(System.currentTimeMillis());
4558 synchronized (sDumpLogs) {
4559 sDumpLogs.add(sDateFormat.format(sDateStamp) + ": " + tag + ", " + log
4560 + (e == null ? "" : (", Exception: " + e)));
4561 }
4562 }
4563 }
4564
4565 public static CustomAppWidget getCustomAppWidget(String name) {
4566 return sCustomAppWidgets.get(name);
4567 }
4568
4569 public static HashMap<String, CustomAppWidget> getCustomAppWidgets() {
4570 return sCustomAppWidgets;
4571 }
4572
4573 public void dumpLogsToLocalData() {
4574 if (DEBUG_DUMP_LOG) {
4575 new AsyncTask<Void, Void, Void>() {
4576 public Void doInBackground(Void ... args) {
4577 boolean success = false;
4578 sDateStamp.setTime(sRunStart);
4579 String FILENAME = sDateStamp.getMonth() + "-"
4580 + sDateStamp.getDay() + "_"
4581 + sDateStamp.getHours() + "-"
4582 + sDateStamp.getMinutes() + "_"
4583 + sDateStamp.getSeconds() + ".txt";
4584
4585 FileOutputStream fos = null;
4586 File outFile = null;
4587 try {
4588 outFile = new File(getFilesDir(), FILENAME);
4589 outFile.createNewFile();
4590 fos = new FileOutputStream(outFile);
4591 } catch (Exception e) {
4592 e.printStackTrace();
4593 }
4594 if (fos != null) {
4595 PrintWriter writer = new PrintWriter(fos);
4596
4597 writer.println(" ");
4598 writer.println("Debug logs: ");
4599 synchronized (sDumpLogs) {
4600 for (int i = 0; i < sDumpLogs.size(); i++) {
4601 writer.println(" " + sDumpLogs.get(i));
4602 }
4603 }
4604 writer.close();
4605 }
4606 try {
4607 if (fos != null) {
4608 fos.close();
4609 success = true;
4610 }
4611 } catch (IOException e) {
4612 e.printStackTrace();
4613 }
4614 return null;
4615 }
4616 }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void) null);
4617 }
4618 }
4619 }
|